diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/appendices/README.md | 4 | ||||
| -rw-r--r-- | docs/appendices/current-spill-matrix.md | 14 | ||||
| -rw-r--r-- | docs/chapter-backports.md | 277 | ||||
| -rw-r--r-- | docs/chapter-split.md | 220 | ||||
| -rw-r--r-- | docs/incremental-builds.md | 63 | ||||
| -rw-r--r-- | docs/japanese-translation.md | 22 | ||||
| -rw-r--r-- | docs/regalloc-spills.md | 2 | ||||
| -rw-r--r-- | docs/roadmap.ja.md | 468 | ||||
| -rw-r--r-- | docs/roadmap.md | 732 | ||||
| -rw-r--r-- | docs/stage-audit.md | 107 |
10 files changed, 267 insertions, 1642 deletions
diff --git a/docs/appendices/README.md b/docs/appendices/README.md index d7803c97..9e39770c 100644 --- a/docs/appendices/README.md +++ b/docs/appendices/README.md @@ -9,7 +9,3 @@ another compiler directory. rematerialization, inlining, and historical measurements from the split's original snapshots. [Measurement methods](../regalloc-spills.md) describe what those numbers include. - -The [roadmap](../roadmap.md), [incremental build design](../incremental-builds.md), -and [review queue](../chapter-backports.md) distinguish future work from current -behavior. The [stage audit](../stage-audit.md) records known limits and cleanup. diff --git a/docs/appendices/current-spill-matrix.md b/docs/appendices/current-spill-matrix.md deleted file mode 100644 index df253a46..00000000 --- a/docs/appendices/current-spill-matrix.md +++ /dev/null @@ -1,14 +0,0 @@ -# Current allocator comparison - -`make spill-stats` measures Chapters 20, 21, 22, 23, 24b, and 28b against the -historical test cohorts. Cohort IDs and test names retain their old numbers; -column headings identify the current compiler directories. - -These are separate from the dated [pre-split measurements](../regalloc-spills.md). -After a successful run, `python build-support/spill-matrix.py --reuse --update-docs` -updates only this table. Fresh logs use 24b/28b names so old 24/25 results cannot -silently become measurements of new compiler snapshots. - -<!-- spill-matrix:start --> -No post-split measurement has been published yet. -<!-- spill-matrix:end --> diff --git a/docs/chapter-backports.md b/docs/chapter-backports.md deleted file mode 100644 index 1c2ffb85..00000000 --- a/docs/chapter-backports.md +++ /dev/null @@ -1,277 +0,0 @@ -# Chapter backport queue - -This file tracks pending backports and deferred work. Completed work and old -validation results belong in Git history. Reusable implementation constraints -live in the [AI notes](../skills/chapter25-codex-notes.md). - -The [completed split](chapter-split.md) places local/interprocedural SCCP in -24a/24b, incomplete types in 25, construction in 26, escape in 27, and -modules/separate compilation in 28a/28b. The combined old 24/25 snapshots are -retired. Do not backport fixes into those retired copies; their replacements -already retain the constructor, alias, native-exposure and import fixes. - -Native library construction and tuning material live in [appendices](appendices/README.md). -Future incremental builds are specified in [incremental-builds.md](incremental-builds.md). - -## Four README intentions to review - -Keep these four issues visible while reviewing the named chapters. Decisions -may change the construction/escape boundary or later chapter splits. - -1. **Construction (active):** final fields should be local SSA values during - construction, including repeated assignments and loops, with field Stores - at helper exits. Silent loss of writes is a bug; reject unsupported cases. - Also decide whether to allow helpers after inlining and defer completion - checks until SCCP. Those are separate from local field updates. - The local-value implementation now supports final data fields in construction, - escape, and modules, including constant defaults and loop Phis. Shared - function declarations remain fixed. Validate this frontier before adjusting - the broader constructor rules. -2. **Privacy:** underscore names select the public interface, but sibling access - to private fields is not yet rejected. Confirm and enforce the intended - source access rules. The proposed chapter boundary and library changes are - recorded below; Cliff reviews that proposal before implementation. -3. **Class initialization:** review the corrected rule: A may start, initialize - B, then resume. Passive references may cycle; active dependencies may not. - Parents finish before children start. The README now matches this behavior; - further semantic changes need an explicit decision. -4. **Escape summaries and ELF visibility:** analysis tracks unknown callers, - while the current ELF writer exports retained named functions. Decide which - symbols should be public and how that boundary relates to separate compilation. - -### Privacy proposal for review - -Introduce enforced underscore privacy in **Modules and Lazy Class -Initialization** (`chapter28a`). This is a visible language change: -earlier snapshots allow underscore field access; from Modules onward, access -must obey the declaring class's boundary. The teaching outline already places -privacy here, alongside class names, ownership, and the public interface. -The earlier escape snapshot does not filter fields by underscore spelling. -Its private construction memory is a different concept. - -Review follow-up: preserve the distinction between export filtering and access -checks. Before the 2026-10-24 cleanup, the first underscore helper in the -numbered snapshots' tests was Chapter 19's `Chapter19Test.testString`, -`_hashCodeString`. Renaming it to -`hashCodeString` produces the identical LocalSched graph there: single-use -inlining removes it regardless of spelling. In Chapter 25's copy of that test, -the same rename retains an extra function Return. Underscore export filtering -currently starts in Chapter 25 (and its Modules extraction), not Chapter 19. -Old test class names inside newer snapshots do not identify when their current -underscore behavior was introduced. Any access-check change must preserve the -existing ability to hide helpers and classes from the public interface. -Chapter 19's example now uses `hashCodeString` and field `hashCode`; all 42 -Chapter19Test tests pass with the existing expected graph unchanged. - -Proposed rules: - -- A field or method beginning with `_` is accessible only from code in its - declaring class, including its constructors and method bodies. Enclosing, - nested, and sibling classes do not gain access to that field. A same-class - method may access another instance's private fields; the receiver need not - be exactly `self`. -- A private class name is available in its declaring scope and within the - class itself. For example, `App` may name its child `_Cache`, but that does - not grant `App` access to `_Cache._data`. Code outside that declaring scope - cannot bypass the restriction with a dotted path. Private ancestors also - keep their descendants out of the public interface. Check both type names - and class-value references, and distinguish naming a private class from - using a public value returned by a factory. -- Reads, writes, updates, and method references follow the same rule. - Preserve the source owner and location when receiver types are unresolved; - inlining must not change who has access. An unused result or a folded Load - must not erase an illegal source access. -- Native declarations obey the same source rules. A public wrapper or alias - may deliberately expose a private native binding; no exception for `sys`. - -Source audit: - -- `sys/io.smp` calls `libc._exit` twice. Keep the native `_exit` declaration - private in `sys/libc.smp`, add `val exit = { i32 status -> _exit(status); };`, - and use `libc.exit` in IO. This preserves the native symbol and behavior. -- Bitset's `_bits` and `_grow`, including `_grow(bit)._bits`, are used within - the declaring struct and remain legal. Array helpers call their own `_grow`. - These are the other underscore names in the bundled module library. -- Several retained String spill fixtures use free functions outside `_String` - to access `_hashCode`. They will need an explicit adaptation in this snapshot - (for example, making that fixture field public), with any changed allocation - expectations measured. Do not silently grant file-wide access to pass them. -- The numbered Chapter 25 library has the same `_exit` accesses, and its - `docs/examples/FileIO.smp` also calls `sys.libc._exit`. - -After approval, implement and test in Modules, update its English/Japanese -READMEs with the explicit language boundary, and run the full module suite -including bundled `sys` and native execution. Cover allowed same-class access, -rejected cross-class access, private class lookup, and forward receiver types. -Carry the rule and library adaptations into Chapter 28b, with import tests -preserving the original source owner. Do not backport the new -language restriction into Chapters 10-24 or the named pre-module snapshots. - -## Investigate next - -Establish an earlier-chapter failure before changing compiler behavior. - -### Narrow stores in the ideal evaluator - -The prefix-update regression exposed an independent evaluator bug. In Chapter -23, this postfix-only example returns 25710 in Eval2 instead of 110: - -```simple -struct C { u8 x=255; val next_noInline={ -> x++; return x; }; }; -var c=new C; -val a=c.next_noInline(); -val b=c.next_noInline(); -return a+10*b+100*c.x; -``` - -`Eval2.store` copies the raw value into its Java object array. It does not -truncate to the field width, even though Store optimization removes masks that -machine stores perform implicitly. The native prefix-update regression checks -that the stored byte wraps and the expression returns the converted value. -Establish the first affected evaluator and check signed fields and floating -rounding before propagating a fix. Do not change valid generated code to match -an incorrect ideal evaluator. - -### Constructor loop with break/continue: worklist assertion - -This fails in construction and in the unchanged escape compiler before the -final-field fix, even with a mutable field: - -```simple -struct C { - int x=0; - new C={int n -> - for(int i=0;i<n;i++) { - if(i==1) continue; - if(i==3) break; - x+=i+1; - } - }; -}; -return new C(arg).x; -``` - -`IterPeeps.progressOnList` asserts during Iter with seed 123. The expected value -for arg 3 is 4. This is independent of final-field mutability; retain the -reproducer while reviewing constructor coverage. Scratch probe: -`build/readme-review/mutable_break.smp`. - -### SCCP function revival: Chapter 24 - -`Opto.linkCG` uses `code.link(fidx)`, which clears a linker entry when its -function is structurally dead. Chapter 25 uses the non-cleaning `_link(fidx)` -while discovering calls, so temporarily disconnected functions can be revived. -Chapter 24 already has `_link`; the question is whether its own unlinking and -SCCP sequence can expose the same failure. - -Trace a function from removal of its unknown-caller edge through discovery of -a real call. Preserve the distinction between unreachable control and a -structurally deleted node. No earlier-chapter failure is established yet. -Dependency propagation already exists in Chapter 24's SCCP; only port a -specific missing dependency with a reproducer. Keep Chapter 25's escape and -external-caller machinery out of this investigation. - -Investigation on 2026-10-19 found no cleanup loss. A shadow Opto checked the -linker entry before and after every `linkCG` lookup while running all 513 -Chapter 24 tests: 787 SCCP runs, 297 lookups, 74 links to functions with only -their null input remaining, and zero structurally dead targets or removed -entries. All tests passed. The remaining null lookups are not evidence of a -cleanup loss: their entries were already null before the lookup. - -The factorial trace shows the distinction: FunNode#1309 loses Start and has -one input, four uses, and type TOP; it then reaches XCONTROL before a real -call links it. `isDead()` stays false, since it requires no inputs, no uses, -and a null type. Thus the observed temporary lack of callers does not trigger -`link()` cleanup. Keep this candidate unconfirmed rather than backporting the -Chapter 25 lookup change on textual differences alone. - -## Future compiler work - -### Deterministic inlining - -The parse-size cutoff can reject a function whose optimized body is small. -After shrinking rewrites converge, choose one growing inline using current -body sizes and a deterministic ranking. Cache sizes per convergence round; -use stable call-site provenance to break ties, not worklist order or newly -allocated node IDs. Avoid per-node dependencies and repeated whole-body scans -while a large function gradually shrinks. - -The bounded boilerplate-inlining heuristic is already in Chapters 20-24. -This item is the larger Chapter 25 redesign. - -### Conversion and BOTTOM-on-error contracts - -Coordinate these changes with the incomplete-type/conversion split: - -- Chapter 24 Loads supply a typed result even when they report an error. - Changing that result to BOTTOM alone breaks incomplete loop typing and can - obscure the intended diagnostic. Chapter 25's Convert nodes provide the - surrounding typed result. Relevant existing cases include - `testBrainFuck`, `testWhileWithNullInside`, `testNullGuardErrors`, and - `testLinkedList0`. -- Chapter 24 arithmetic also needs its typed fallback while forward references - are unresolved. Returning BOTTOM confuses unresolved expressions with - uninitialized fields (`testCoRecur`) and loses arithmetic diagnostics. - -Preserve the current contracts until the parser, conversions, loop bounds, -and diagnostics can move together. Do not weaken narrowing assertions or -expected errors to make an isolated backport pass. - -### Representation changes - -The approved split keeps incomplete types, numeric modes, conversions, guards, -and symbolic fields together, then introduces private constructor memory in -the construction chapter. They are not independent fixes to copy into earlier -snapshots. - -Serialization, global identity remapping, and imported escape summaries belong -with separate compilation. Escape analysis now has its own earlier snapshot after -construction; keep its boundary open to review. - -### Selective loading of library IR - -Reliable incremental rebuilding is separate future work; see the -[incremental build design](incremental-builds.md) for the stale-dependency -example, required input tracking, rebuild propagation, and validation plan. - -The Separate Compilation README review found that importing only `sys.io.p` -still loads all ten units in the bundled `sys.o`, including `aryu8`, `aryi64`, -`scan`, and `adt.bitset`. All remain loaded through TypeCheck. Loading only -needed library members is a desired improvement, not current behavior. - -Selection works between independent objects: a client of `Counters.o` leaves -an unrelated `Unused.o` unloaded. Once selected, an object's entire saved IR -is reconstructed, and `ParseAll.loadDeps` follows its recorded dependencies. -Splitting the object alone is insufficient while the root's reexports and -unused function bodies still pull in those dependencies. Design member-level -IR loading and dependency discovery together; preserve type identity, -initialization rules, and imported escape summaries. This is separate from -whether imported code is initialized or emitted in the client's object. - -## Modules and separate compilation - -Class-cycle checking and CLInit runtime lowering belong in 28a/28b. The -[module README](../chapter28a/README.md) explains their semantics; remaining -argument, initialization and pointer plans live in the [roadmap](roadmap.md). -Native code cannot receive classes; a native-startup/class-handoff policy is -not planned. Current data pointers remain eight bytes. - -## Backport validation - -Introduce an independent fix in the earliest applicable chapter and propagate -it through every affected snapshot. For correctness fixes, first reproduce the -failure in the destination compiler. Preserve each chapter's representation -and tutorial boundary. - -Run focused checks and the existing full Make suites for the affected compiler -directories, for example: - -```sh -make -k tests CHAPTERS="chapter20 chapter21" -``` - -Chapter 25's inherited tests do not validate earlier compiler implementations. -Force rebuilds when older Makefiles miss dependencies, and rebuild serialized -libraries when their compiler or object format changes. Keep execution and -register-legality checks independent of spill expectations. Preserve unrelated -edits, keep text LF-only, and run `git diff --check`. diff --git a/docs/chapter-split.md b/docs/chapter-split.md deleted file mode 100644 index 55551b27..00000000 --- a/docs/chapter-split.md +++ /dev/null @@ -1,220 +0,0 @@ -# Approved chapter split - -Cliff approved the seven-part split on 2026-10-19 and the final numbering on -2026-10-24. The old combined Chapters 24 and 25 are retired; their history is -available in Git. Every new directory is a complete compiler snapshot. - -| Previous directory | Final chapter | -|---|---| -| chapter-sccp | 24a: SCCP within a function | -| chapter-interprocedural-sccp | 24b: SCCP across functions | -| chapter-incomplete-types | 25: SSA before types are known | -| chapter-construction | 26: Construction and private memory | -| chapter-escape | 27: Escape analysis and unknown callers | -| chapter-modules | 28a: Modules and lazy class initialization | -| chapter-separate-compilation | 28b: Separate compilation | - -Keep 17a/17b. The other paired chapters extend one idea: 24a/24b goes from -local to interprocedural SCCP; 28a/28b goes from compiling modules together to -compiling them independently. Construction and escape remain separate topics; -neither is presented as requiring the other. - -Inherited Java test names such as `Chapter24Test` and `Chapter25Test` retain -their historical identities across snapshots. They are regression cohorts, -not an index of the current teaching sequence. Historical allocator tables -also retain their original labels and dates. - -Native library construction and optimizer tuning are now [appendices](appendices/README.md). -The [roadmap](roadmap.md) and [backport queue](chapter-backports.md) own future work. - -The teaching pattern is to introduce a difficult idea, then show how to make -it useful in a larger setting. Each chapter must remain a complete, runnable -compiler snapshot that preserves the language supported by its predecessor. - -## Teaching sequence - -| Topic | Main idea | Endpoint | -|---|---|---| -| SCCP within a function | Optimistic types, reachable control, Phi inputs, worklists, fixed points, and loop widening. Keep calls conservative at their interfaces. | Fold loop-carried values and eliminate branches that pessimistic iteration cannot resolve. | -| SCCP across functions | Discover call edges during analysis; merge arguments and returns; account for unknown callers and function pointers. | Specialize parameters, resolve call targets, handle recursion, and remove unreachable functions. | -| SSA before types are known | Syntax and lexical binding choose graph shape. Keep scalar types, deferred numeric modes, conversions, guards, incomplete objects, symbolic fields, and removal of parser-supplied Load/Phi bounds together. | Compile forward definitions without provisional types determining graph topology; settle types and layouts afterward. | -| Construction and private memory | Allocation helpers, hidden initializers, user constructors, private self-memory, field-completion checks, and explicit publication. | Preserve initialization and memory facts through constructor calls and publish a completed object. | -| Escape analysis and unknown callers | Discover which aliases and functions can reach code outside the known graph. | Preserve exposed memory and callable functions while specializing private code. | -| Modules and lazy class initialization | Multiple source files, namespaces, privacy, ownership, singleton storage, first-use initialization, parent order, cycle diagnostics, and lowering. | Compile sources together with defined class-initialization behavior. | -| Separate compilation | Preserve types, ideal IR, and identities across independent compilations. | Build a library and client separately, optimize imported bodies, link remaining calls, and check initialization dependencies across imports. | - -The first incomplete-type chapter stays together. Its unresolved memory accesses -must work before construction introduces private memory; defer the private -construction protocol, not the machinery required to represent an unknown access. - -## SCCP boundary - -The first SCCP snapshot keeps unknown-caller Start inputs and disconnects call -edges during the optimistic solve. Parameters retain their declared types; -call results retain facts from the preceding pessimistic pass, which can be -sharper than the written signature. Calls still have whole-memory effects. -After SCCP, peepholes relink calls and FinalizeIR removes uncalled bodies while -preserving named exports. This is an intermediate compiler design, not a -textual division of Opto.java. - -Ordinary loop widening belongs in the first chapter. The second adds argument -and result widening for recursion. Use `f(x-1)+1` to explain why these are -separate data cycles: the recursive return value never crosses Parm(x). - -Chained comparisons do not need their own chapter. Their home is -beside Chapter 23 short-circuit expressions: evaluate each operand -once and evaluate later operands only when earlier comparisons succeed. -The current Chapter 24 cold-loop-split allocator improvement belongs with the -tuning appendix. - -## Construction and escape boundary to establish - -Separate an explicit memory-publication operation from the analysis that -discovers exposure to unknown code. - -| Construction chapter | Escape-analysis chapter | -|---|---| -| Tracks private memory while an object is built. | Tracks aliases and functions exposed through memory, calls, and returns. | -| Publishes initialized memory at an explicit boundary. | Propagates exposure through the reachable graph. | -| Conservatively preserves unknown callers for surviving function addresses, following the current Chapter 24 policy. | Uses escape facts to determine which functions need unknown callers. | -| Explains EscapeNode as a publication operation. | Explains the Start/Stop feedback cycle between known code and the external world. | - -Verify this split against constructor calls and memory summaries. The earlier -snapshot can publish conservatively; the later one refines that behavior. -The name EscapeNode does not require teaching the full escape analysis at -the point where publication is introduced. - -`chapter26` now implements that explicit boundary. Hidden initializer -and allocator functions carry whole private object memory; completion publishes -fields into public aliases. The new constructor functions reject early passing -or storing of `self`, including self-links, and access through a merged alias. -These are deliberate limits for review with the next escape snapshot. Normal -function addresses retain conservative unknown callers. Initializers capture -enclosing values as hidden arguments; writing back to captures is rejected. -Arrays follow the same construction/publication protocol. There are no modules -or class singletons in this snapshot. - -Review on 2026-10-22 removed an unnecessary representation detour. Constructor -fields now stay in local SSA values and ordinary Phis; helpers emit ordinary -Stores at exit, with precise aliases gathered in MemMerge. Private memory is -only the handoff between helpers and publication. No global escape discovery -is needed to build that representation. - -Construction and escape discovery need not be taught in this order. Keep the -current order provisionally, without inventing a constructor example to claim -that dependency. The callback example now lives in the EA chapter. - -## Modules before separate compilation - -Teach module semantics with all source files compiled in one invocation. -Include lazy initialization, parent requirements, and cycle checking there. -Separate compilation then adds serialized ideal IR and types, GlobalBits -identity remapping, imported type reconstruction and analysis, and Unlink -before machine compilation. Extend initialization checks across imported -bodies, preserving opaque CLInit nodes until lowering. - -Keep code ownership distinct from unknown-caller edges in both chapters. -Keeping a function for emission must not imply arbitrary callers can reach it. - -## Appendices and future work - -- Native library construction: numeric C data symbols, address handling, - modular sys, runtime and driver support, and larger examples. Preserve the - native functionality already taught in earlier chapters. -- Optimizer and backend tuning: existing cloning/inlining choices, spill - ranking by live-range area and copy cost, rematerialization, and splitting - policies. Compare equivalent programs when explaining costs. - -This split organizes implemented behavior. Deterministic inlining, argument -arrays, static pre-initialization of classes, CFG frequency estimates, and -constant instance-field storage remain future work. See the -[backport queue](chapter-backports.md) and [roadmap](roadmap.md). - -## Renumbering and validation - -The first runnable extractions are -[SCCP Within a Function](../chapter24a/README.md) and -[SCCP Across Functions](../chapter24b/README.md), followed by -[SSA Before Types Are Known](../chapter25/README.md), -[Construction and Private Memory](../chapter26/README.md) and -[Escape Analysis and Unknown Callers](../chapter27/README.md), and -[Modules and Lazy Class Initialization](../chapter28a/README.md), and -[Separate Compilation](../chapter28b/README.md). -All seven main snapshots are extracted and numbered. Chained comparisons now -start in Chapter 23 beside short-circuit expressions. The linear-history -publisher follows the numbered directories, including lettered pairs. - -Validate each intermediate compiler with its own Make suite, including execution -checks. Keep declarations, conversions, diagnostics, and type bounds consistent; -do not weaken checks to make an intermediate snapshot pass. Rebuild serialized -libraries when their compiler or object format changes. - -## Escape snapshot ready for boundary review - -`chapter27` adds function/alias exposure sets to TypeMem and closes the -Start/Stop feedback cycle during SCCP. Known calls propagate memory summaries; -exposed returns, fields, external arguments and native calls discover further -addresses. Native calls contribute even on paths with no live Return. Function -addresses kept entirely in known code no longer imply unknown callers. - -The [recursive callback example](../chapter27/README.md) now belongs in -that chapter. Its base parameter becomes 2 while recursive calls remain. -Returning the callback or a containing object, or handing it to native code, -restores conservative arguments. FinalizeIR separates those analysis facts -from the roots needed to emit code. - -Construction still keeps local field values and rejects early exposure of self. -Both runnable snapshots are available for Cliff's promised review/play session. -This extraction does not choose broader rules for partially initialized objects. -No modules, class singletons, serialized IR or identity remapping are included. - -## Modules snapshot ready for review - -`chapter28a` compiles all discovered source files together. It adds class -singletons, namespaces and export filtering, parent initialization, CLInit memory effects, -cycle diagnostics, runtime lowering, and shared native class storage. Source -discovery reads parents as well as referenced children. The bundled modular sys -is supplied as source; building reusable libraries remains appendix material. - -There is no Serialize phase, object reader, GlobalBits remapping, or imported -IR. LocalIds assigns dense IDs within the invocation. FinishCalls detaches -analysis call edges after loop analysis, before scheduling or instruction -selection. ELF emits all discovered live bodies in the selected entry object. - -The preceding construction representation remains: field values and loop Phis -stay local, with Stores at helper exits. Constructor reads may see an unfinished -object, but may never read an uninitialized field. The module snapshot checks -this separately from completion and publication. Escape analysis still restores -unknown callers when a private callback reaches native code, including on paths -with no Return. - -The README's lazy lookup-table example is executable. Existing tests also cover -source/type cycles, rejected initialization cycles, parent order, native cold/hot -calls, shared storage, and ARM/RISC-V encoding. The construction/escape border -remains open to Cliff's review; this extraction does not settle broader rules for -publishing unfinished objects. - -## Separate compilation snapshot ready for review - -`chapter28b` adds saved types and ideal IR, `GlobalBits` -identity remapping, object discovery, imported bodies, and `Unlink`. It carries -Modules' deferred constructor parsing, local final-field values and loop Phis, -converted private memory summaries, native callback exposure, and source-parent -initialization order. Source privacy enforcement remains a separate review. - -The runnable example compiles a library and a client from separate source roots. -The client allocates an imported struct whose constructor updates a final field -in a loop, then calls a retained library function that updates shared state. -The regression checks evaluation under three seeds and native cold/repeated -calls. A second import test preserves unknown callers for a callback passed to -native code. Existing import tests retain initialization-cycle diagnostics, -shared class data, public type reconstruction, and native function thunks. - -Serialization now saves the class declarations of owned nested structs even -when they are absent from the optimized graph's type closure. Otherwise a -client can load the instance type but cannot find its allocation signature. -An explicit source input is always recompiled; dependency objects may still -be reused. The old incremental-rebuild experiment remains ignored. - -All seven main teaching boundaries now have numbered snapshots. Broader -language choices and compiler improvements remain in the review queue. diff --git a/docs/incremental-builds.md b/docs/incremental-builds.md deleted file mode 100644 index 201922a0..00000000 --- a/docs/incremental-builds.md +++ /dev/null @@ -1,63 +0,0 @@ -# Incremental builds: future work - -Recorded after the Separate Compilation README review, 2026-10-24. This is a -design outline, not a description of implemented incremental builds. - -## Current gap - -`CompUnit` decides whether to reuse an object when the unit is discovered. It -checks its source timestamp and its dependencies' existing object timestamps. -It does not settle the whole dependency graph before accepting saved IR. - -For example: - -1. A depends on B and has inlined some of B. -2. B's source changes, leaving B's object unchanged. -3. A client discovers A. A's check sees the old B object and accepts A's object. -4. Later discovery notices that B needs rebuilding, without reconsidering A. - -The compiler can therefore reuse code based on stale bodies or constants. -An unchanged public signature is insufficient: Simple imports implementation -details for optimization as well as declarations for type checking. - -## Required pieces - -- Discover dependencies, mark changed units, and propagate that decision to - dependents until it settles. Handle cycles as groups. Do this before loading - IR that may be stale; newly discovered dependencies must also join the plan. -- Record actual inputs. An object can own several source units, so track all - of their sources and the objects resolved through library search paths. - The current check assumes `buildDir/name.o`; a missing file has timestamp - zero and does not trigger rebuilding. Missing inputs must invalidate or - produce a useful error rather than silently accepting an old result. -- Record compiler and serialization versions, target, ABI, and relevant - options. Content hashes would also detect changes with preserved timestamps. - Specify which changes require rebuilding source and which reject an - incompatible source-free library. -- Make build targets match output ownership. The old ignored `testModule0` - expects separate A.o and A/B.o, while the current compiler can bundle several - units into one object. The target must identify the complete set it owns. -- Provide an "ensure current" build operation. Explicit source compilation - currently always recompiles. An unchanged build should do no compilation or - linking; a changed object or link input should trigger relinking. -- Publish successful outputs without leaving a partial object accepted after - failure, and keep the recorded inputs consistent with those outputs. - -Initially, rebuilding every dependent of a changed object is a reasonable -conservative rule. Fine-grained interface/body fingerprints can come later. - -## Suggested first implementation - -Put a small build driver around the compiler with explicit object targets, -recorded inputs, and dependency propagation. Keep individual compilation -requests straightforward. A clean build and an incremental build must produce -the same observable program behavior. - -Cover unchanged builds, direct and transitive source edits, inlined constants -and bodies, cyclic dependencies, bundled sources, removed inputs, library-path -changes, compiler/target changes, and failed builds. Use controlled timestamps -or hashes instead of sleeps. Replace the ignored historical experiment with -tests matching the chosen object ownership model. - -Selective library IR loading is separate future work. It can reduce compile -cost, but is not required to make conservative incremental rebuilding correct. diff --git a/docs/japanese-translation.md b/docs/japanese-translation.md deleted file mode 100644 index 39383345..00000000 --- a/docs/japanese-translation.md +++ /dev/null @@ -1,22 +0,0 @@ -# Japanese translation / 翻訳の保守方針 - -English is authoritative. The contributed Japanese translation is maintained on -a best-effort basis; English edits and chapter reorganizations never wait for -translation updates. Move Japanese files and update navigation when renumbering -chapters; prose updates can follow separately. Contributions are welcome, without -an ongoing maintenance obligation for the original translator. - -[英語版](../README.md)が正式な文書です。この日本語訳は寄贈されたもので、可能な範囲で保守されますが、 -英語版より更新が遅れることがあります。英語版の編集や章の再構成を、翻訳の更新待ちにすることはありません。 -章を移動・改番する際は、日本語ファイルも一緒に移動して章の見出しやリンクを更新し、本文の翻訳は後から更新できます。 -翻訳の改善は歓迎しますが、元の寄贈者に継続的な保守を義務付けるものではありません。 - -初回の翻訳は英語版のコミット -[`81b654d7`](https://github.com/SeaOfNodes/Simple/tree/81b654d76444d747fdf1d053760e507b50f26e18) -を基にしています。2026年10月の目次・文法見出し・コード例・命令エンコーディング・ -`C0DE` ヘッダに関する文書修正も反映しています。 -各文書の English リンクは現在の英語版を指します。更新されていない箇所の原文を確認するには、 -上記コミットの対応するファイルを参照してください。新しい章が未翻訳の場合は、その旨を示して英語版にリンクできます。 - - -[日本語の紹介に戻る](../README.ja.md) diff --git a/docs/regalloc-spills.md b/docs/regalloc-spills.md index 71bab467..a4ca818f 100644 --- a/docs/regalloc-spills.md +++ b/docs/regalloc-spills.md @@ -3,7 +3,7 @@ This is the dated **pre-split** comparison. Its Ch 24/25 columns describe the retired combined snapshots, not today's Chapter 25 (incomplete types). Current measurement tools target 24b/28b and publish a -[separate table](appendices/current-spill-matrix.md). The historical numbers +[separate table](../skills/current-spill-matrix.md). The historical numbers below remain unchanged. Read a row left to right to follow a fixed cohort through successive compilers. diff --git a/docs/roadmap.ja.md b/docs/roadmap.ja.md index 0af228a5..964ff81c 100644 --- a/docs/roadmap.ja.md +++ b/docs/roadmap.ja.md @@ -1,455 +1,33 @@ -# ロードマップ - -この設計ノートは旧第25章から移したものです。初期の節には、現在の第25〜28b章で -実装済みの機能について検討した案も含みます。すべてが未実装という意味ではありません。 -現状と残る課題は[監査記録](stage-audit.md)、増分ビルドは[設計メモ](incremental-builds.md)を参照してください。 +# コンパイラのロードマップ [English](roadmap.md) | 日本語 -この文書は [ROADMAP.md](roadmap.md) の日本語訳です。原文の設計案と未解決の問いを、そのまま設計案として記しています。 - -## 分割コンパイル - -未知の前方参照をどう扱うか? H-M は使わないので、単一化も行わない。 -関数以外の前方参照を認めないことにする……関数なら、定義した時点ですぐにシグネチャを提供できる。 - -案 A:FRefNode を削除し、すべてのバグを直す。 -案 A の問題:相互再帰を使えるようにしたい。 -例:`val even = { int x -> x ? odd(x-1) : true; }; val odd = { int x -> x ? even(x-1) : false; };` - -案 B:型は順序どおりに定義する必要があるが、それ以外の関数には順序を要求しない。 -`new T` ならエラーにできるが、`fref.field`、`fref()`、`fref op fref`、`fref[]` ではできない。 - -あらゆる場所で Type.BOTTOM をパーサーの要件に合わせて「引き上げる」必要がある。 -FRef からフィールドをロードするには、FREF を FIELD のある TSTRUCT への TMP に引き上げる必要がある……。 -実際には、FREF が正しくなると局所的に仮定するだけだ。型は任意のコードから来るので、 -アクセスできず、引き上げられない。TMP は nullable かもしれない(後でエラーにすべき)。 -TSTRUCT は無名にでき、無名になるだろう。これは、すべての TSTRUCT を無名にすることを意味する。 - -問題は、未知・遠隔・外部・前方の参照にある。 - -FRef を*少しでも*認めると、すべての処理が FRef 型に遭遇することを考慮しなければならない。 -同じスコープの関数で認めると、上の odd/even の例が使える。 -同じスコープの構造体で認めると、相互再帰的な構造体が使える。 -スコープの外へ昇格させる際には外部の検索を試み、見つからなければ失敗する。 - -任意の使い方の例:`sys.fld`。`sys` の検索に失敗したら、次のどちらかを推測しなければならない。 -前方参照で問題なく、間もなく定義されるのか……それとも今すぐ外部を検索するのか。 -さらに、使い方によっては問題の発覚が遅れる。 -```java -// Extern or Future `sys` being returned. -val foo { -> return sys; } - -// foo() returns FRef, so Bottom, so the `.bar` has no type -// to do field lookups -val main { arg -> foo().bar } - -// HERE: Parser finds `sys` OR NOT?!?!?! -val sys??? = user_stuff....; - -``` - -行き着く先は、パーサーが一切エラーを出さず、盲目的な AST のように SSA を構築し、 -不適切な型だらけのグラフを結線するだけになることだ。 -必要なこと:パーサーを導く型をなくす! 構文だけを使う! - -(明らかな小さな問題:浮動小数点数へ拡張するかどうか。) -修正:たとえば整数演算を挿入する。両方の入力が isa-Int であり、isa-Float に引き上げられなくなるまでは、 -戻り値の型を Int と Float の MEET とする。 - -メソッド呼び出しで起こり得る問題: -`UNKNOWN_STRUCT_BOT.UNKNOWN_FIELD ( ARGS ); // Insert SELF OR NOT?` -— SELF を挿入するか? -修正案:常に SELF 引数があると仮定し、静的と証明できる場合は null にする。 -最適化:定数パラメータをシグネチャから削除するか? *定数*パラメータは、 -*パーサー*の時点で定数でなければならない。そうでなければ未知の呼び出し元がないことが必要になる -(したがって局所的な最適化になる)。 -修正案:SELF 引数を*最初*ではなく*最後*に置くと、不要になった場合に削除しやすいか? - -Load は ConFldOff を捨て、名前を保持する。 -Load はスケーリング前の添字を保持し、添字入力を持つ。構造体ではこの入力は null。 -Ptr Base が*確定*した後で基底の配置を行い、添字の計算を畳み込める。 - -- `Load(ctrl, base, null );` — フィールド名は内部に持つ。 -- `Load(ctrl, base, index_math );` — フィールド名は `[]`。 -- `Store(ctrl, base, null , val);` - -問題:エイリアス情報がない! - -案 C:直接呼び出し `odd()` と型では FRef を認める。それ以外はすべて*引き上げ*が必要。 - -## 構造体と配列の統合 - -構造体のフィールドは名前で索引付けされ、配列の要素は数値で索引付けされる。 -この2つの考え方は両立する、という発想です。 -C 言語では古くからこの考え方を使っています。通常の C の構造体の後に配列を続け、 -通常の配列構文で索引アクセスできます。 - -別の方法として、配列へのポインタをフィールドに保持すると、間接参照が1つ増える代わりに柔軟性が得られます。 -特に、配列を(通常はより大きな配列に)交換できます。 -これは、広く使われる汎用コンテナ `Vector` や `ArrayList` の基礎です。 - -```java -struct str { - int hash; // A cache of the strings hash code - u32 #; // The array length - u8 []; // Array of bytes -}; - -str X = "abc"; -print(X.hash); // Read the hash field -X[0]=='a'; // Read array element 0 -X#; // Length of the array - -``` - -ここでは、`#` というフィールドが配列の長さを保持し、`[]` というフィールドが実際の配列です。 -メモリ内では、この配列がほかのフィールドの直後に続きます。 -可変長配列の別の例を示します。 - -```java -struct vecInt { - u32 size; // Actual number of elements - u32 #; // The array length or capacity - int []; // Array of integers. -}; -``` - -明らかな制約として、配列のサイズを直接変更することはできません。 -先頭のフィールドと同じメモリブロックに実際に配置されているからです。 -このような `vecInt` を、より大きい新しい `vecInt` に*再割り当て*することはできますが、 -元の `vecInt` は解放されます。同じ `vecInt` の参照を永遠に使い続けることはできません。 - -名前付きフィールドはいくつあってもかまいません。 - -```java -struct Class { - !str !className; - !str !professor; - int time; - int credits; - !Class !prerequisite; - u16 #; // Limit of 65535 students per class - Student []; -}; -``` - -自己参照も使えます。 - -```java -struct NTree { - Some val; - u8 #; // Length is 0-255 - NTree []; // More NTrees -}; -``` - -構文規則は次のとおりです。 - -- 最後のフィールドの名前は `[]` で、どの型でもよい。 -- 最後から2番目のフィールドの名前は `#` で、配列の最大長を表す符号なし整数型でなければならない。 -- ` []` の前に空白が必要になるかもしれない。 - -### コンストラクタと配列 - -割り当てには配列の長さを含め、コンストラクタも含められます。 -`new NTree{val=17;}[2];` - -コンストラクタは省略できますが、配列の長さは省略できません。 - -配列を初期化するための任意の関数を使うと、一度だけ書き込む final 配列を作れます。 -`new u8[buffer#,{ u32 idx -> buffer[idx].toUpperCase() }]` - -### 配列の最大長の変更 - -範囲チェックのある小さな配列を大量に使う場合、配列の長さのために丸々4バイトを確保すると、 -配列のオーバーヘッドが大きく増えることが問題になります。 -Simple では最大長を小さくできます。ただし、最大長が異なる配列は別の型として扱います。 - -```java -struct x0 { u8 #; u8[]; }; // Array limited to 0-255 length -struct x1 { u16 #; u8[]; }; // Array limited to 0-65535 length -struct x2 { u32 #; u8[]; }; // -struct x3 { u64 #; u8[]; }; // - -x0 x = "abc"; // 4 bytes total: [3, 'a', 'b', 'c'] -x1 y = "def"; // 6 bytes total: [3, 0 , 'd', 'e', 'f', 0,]; 1 pad byte to acheieve 2-byte alignment -x = y; // ERROR, "x1 is not x0", no auto-widening -x += y; // OK, 7 bytes total: [6, 'a', 'b', 'c', 'd', 'e', 'f'] -y += x; // OK, 8 bytes total: [6, 0 , 'd', 'e', 'f', 'a', 'b', 'c'] - -x += "VeryLongString.... ....255_chars"; // Runtime RangeCheck - -// Explicit conversion to widen string -y = "" + x; -``` - -### 拡張可能な配列・文字列の2つの形:間接参照を増やすかどうか - -間接参照を1つ増やすと、Java の `ArrayList` や C++ の `Vector` のようになります。 - -増やさない場合、拡張可能な配列を伸ばす呼び出しは、新しいオブジェクトを返す可能性があります。 -拡張は必要な場合だけ行います。 - -```java -val vec1 = new vecInt[0].add(2); // len=1, capacity=1 -val vec2 = vec1.add(3); // NEW !vecInt !returned, only is deleted; len=2; capacity=2; -vec1[0]; // ERROR, vec1 has been freed by call to `vec1.add(3)` -vec2[0]; // OK -vec2[1]; // OK -vec2[2]; // Runtime error, AIOOBE -``` - -## 文字列の実装 - -既定の文字列は「str」「cstr」「ustr」などの名前を持ちます。 - -常に unicode8 を使い、u8 を明示的に操作します。 -Unicode の「文字」は、基礎となる実装の上に関数呼び出しとして提供します。 -おそらく別の文字列クラスで実装することになります。 - -## 既定の文字列 - -連結によって効率的に拡張でき、さまざまな IO/printf 関数をサポートします。 -明示的に要求すれば、基礎となる配列を可変な状態で共有できます。 -既定では常に、非共有時は可変、共有時は読み取り専用です。 - -```java -struct str { - - // Add a string to the string, possibly growing - val add = { str str -> - val self = this; - for( int i=0; i<str#; i++ ) - self = self.add(str[i]); - return self; - }; - - // Add an char to the string, possibly growing - val add = { u8 e -> - if( len >= # ) return copy(# ? #*2 : 1).add(e); - [size++] = e; - return this; - }; - - // Copy 'this' to a new larger size - val copy = { int sz2 -> - val v2 = new str[sz2]; - for( int i=0; i<len; i++ ) - v2[i] = [i]; - v2.len = len; - sys.libc.free(this); // Needs a better Mem Management solution - return v2; - }; - - val print { -> sys.libc.write(1/*stdout*/,[]/*array base as argument to C*/,len); return this; } - - u32 len; // In-use size - u32 #; // Max length is 4Gig, although variants can request smaller lengths - u8 []; // Character data -}; -``` - -### Final な文字列 - -**不変**な文字列を意図しており、**安全な**文字列の基礎にも使えるかもしれません。 -一度に全体を作成する必要があり、おそらく防御的コピーも必要になります。 - -```java -struct sstr { - // Possible static call syntax; no references to "this" - val make { str str -> new sstr[str#,str.at] }; - - // Array contents are immutable. - // Still figuring out good syntax for this. - u8 #; - u8 [~]; // Middlin '~'? Makes primitive array contents *immutable* and requires constructor syntax -}; - -// Construction requires a function that produces the array contents. -// Here we are defining a defensive copy over `buf`. -sstr safe = new sstr[buf.len,{ int idx -> buf[idx]; }]; -sstr safe = sstr.make("abc"); // Defensive copy is made -``` - -### 拡張可能な文字列(StringBuilder または StringBuffer) - -この API は `StringBuilder` スタイルの代替としてそのまま使えることを意図しており、 -使うたびに間接参照が1つ増えます。 - -```java -struct xstr { - !str !_str; // The extra indirection is here - val add = { str str -> _str = str.add2(str); }; - val write = { int fd -> _str.write(fd); }; -}; -``` - -### Unicode 文字列 - -この API は、通常の `str` の上で Unicode の文字を操作できるようにすることを意図しています。 - -```java -struct ustr { - // Assuming idx is at the start of a character, return the character - // as a 32b integer. Returns junk integer if not at a character start. - // For all ASCII strings this will amount to a simple byte load. - // For bytes with high order bits set, this may read 1-4 characters. - val at = { int idx -> ... }; -}; -// Unicode character iterator -struct ucharator { -// Return successive unicode character codes -}; -``` - -### 「C」文字列 - -ゼロ終端の文字列ですが、長さを直接取得できる必要があります。 - -```java -struct cstr { - u8 #; // length including the trailing zero - u8[]; // always ends in a zero byte; suitable for direct passing to C string functions -}; -``` - -### 「german string」:レジスタに格納できる非常に短い文字列 - -メモリ内では、長さを表す1バイト(3ビット?)に続いて0〜7文字を格納し、最大8文字です。 -64ビットのレジスタに収まります。 - -```java -struct gstr { // aka germanString, or blend string and register "streg" - u3 #; // Size is 0-7 bytes??? - u8 []; // Up to 7 bytes -} -// Using inlined object (see below). All these strings are short and are -// represented as packed integers in a 64b register. -*gstr prize1 = "gold"; -*gstr prize2 = "silver"; -*gstr prize3 = "bronze"; - -``` - -### Main は `val main = { str[] args -> ...}` になる - -## 構造体のインライン化 - -構造体は通常、参照として扱いますが、「内容(contents of)」と読む `*` 演算子を使って -値として扱う形へ変換できます(この名前は、たとえば `inline` に変えるかもしれません)。 - -これは名前空間の変更だけです。内部の構造体の寿命を完全に消費しない限り、そのアドレスは取得できません。 -「外部への流出」は認めません。 - -```java -// 2 64-bit words, no other overheads -struct Complex { f64 x,y; val len = { ->Math.sqrt(x*x+y*y); }; }; - -// Current Simple rules: always by-reference -struct ByRef { - !Complex !c; // 4-byte pointer to a Complex -}; -print(new ByRef.c.y); // Lookup requires 1 extra memory load from ref to c - -// New behavior: by value (inlining a struct) -// The '*' syntax indicator can be something else, e.g. a keyword "inline" -// I am pronouncing '*' as "contents of" -struct ByValue { - *!Complex !c; // c is inlined, full 16 bytes into ByValue -}; -print(new ByValue.c.y); // x,y inlined into ByValue, no extra memory load - -// Mixing Refs and Struct Values. Basically, a ref can be converted -// to a value by taking the "contents of" the ref, and values cannot -// be *converted* to a ref, although they can be copied over a ref. -ref. c = val.c; // Error, mixing refs and values -ref.*c = val.c; // Allowed, whole structure copy. Does not allocate, requires ref.c not-null -val. c = ref.c; // Error, mixing refs and values -val. c = *ref.c; // Allowed, whole structure copy -ref. c = ref.c; // Allowed, pointer copy -val. c = val.c; // Allowed, whole structure copy. -val *c = val.c; // Error, cannot take "contents of" a value - -// Alternative syntax, to avoid the "contents of" being on LHS and field applying to being on RHS: -ref.c = val.*c; // Assign into ref.c the "contents of" val.c - -// Arrays of inlined structures -var ary = new *Complex[99]; // Array of 99 !Complex !objects, inlined - -// Calling methods has the same syntax -ref.c.len(); -val.c.len(); - -// Function arguments use the same typing, so can pass-by-value -val math.sin = { *Complex c -> ... }; // Passes a complex by value -math.sin(ref.*c); // Requires de-reference to pass by value -math.sin(val. c); // Pass by value - -``` - -## ほかの章のロードマップ - -`&&` と `||`。 -`int` を `i32` に縮小する。 -`TypeMemPtr` を4バイトに縮小する。`mmap` を使い、ヒープを下位4Gの領域に置く。 -パーサーの既定の for ループ構築、または部分的なループ剥離。 -`for( init; test; next ) body` は、次のようになる。 -```java -{ init; // Normal scope entry to bound lifetime of index variable -if( !test ) { // Zero-trip count test - do { - body; - next; - } while( test ); // Exit test at loop bottom by default -} }; -``` - -## `&&` と `||` - -論理演算子 &&(AND)と ||(OR)は短絡評価を行います。 -最初の条件だけで結果が決まるときは、第2の条件の評価を省略できます。 - -- `&&` は最初のオペランドが偽なら評価を止めます。 -- `||` は最初のオペランドが真なら評価を止めます。 - -これらは制御フローを読みやすくする糖衣構文であり、実行モデルに新しいノードを作りません。 - -`&&` と `||` の構文は、制御フローだけを変える単純な糖衣構文であり、新しいノードを導入しません。 - -次の例を考えます。 -```java -int a = 1; -int b = 0; +未完了の作業と将来の言語設計案の要約です。現在の言語仕様や実装済みの機能を +示すものではありません。詳細と最新の判断は[英語版](roadmap.md)を参照してください。 -if(a++ || b++ ) { - if(b == 0 && a == 2) { - sys.io.p("Or"); - } -} else{ - sys.io.p("And"); -} -return 0; -``` +## 正確性とコンパイル -- `a++` は1(真)と評価されるので、`b++` は評価されません。 -- その結果、`b` は0のままで、`a` は2になり、「Or」が表示されます。 +- 第25〜27章で、メソッドが狭いフィールドを更新した後に切り詰め前の値を返す問題を修正する。 +- 依存関係全体を調べ、変更を伝播してから保存 IR を再利用する、正しい増分ビルドを実装する。 +- ライブラリオブジェクト全体ではなく、必要なメンバーの IR を選択してロードする。 +- 最適化後の関数サイズと安定した順位を使い、インライン化の選択を決定的にする。 +- CFG の実行頻度推定、ループの回転、定数インスタンスフィールドの格納省略を検討する。 -```java -int a = 1; -int b = 1; +## 言語とランタイム -int x=1; -int y=1; -int z=0; +- アンダースコアによる公開インターフェースの選別に加え、ソースの private アクセス規則を決めて強制する。 +- 未完成の self をヘルパーへ渡せる条件と、コンストラクタの完了検査を行う時点を検討する。 +- ELF シンボルの可視性と、未知の呼び出し元・コード出力の所有権を区別する。 +- クラス初期化子の整数引数を引数配列に置き換える。 +- 初期化全体を静的データで表せるクラスについて、実行時の初期化呼び出しと状態を省略する。 +- コンパクトなコード/データ領域、ネイティブポインタ型、GC に依存しない寿命管理を設計する。 -int g = x++ && y++ && z++; -... -``` +## データとライブラリの設計案 -この例では、次のようになります。 +- 名前付きフィールドの末尾に配列を配置する構造体と、異なる幅の長さフィールド。 +- 要素を生成する関数を使う配列初期化。 +- 可変・不変・C 互換・Unicode・短い値文字列の各用途に応じた文字列ライブラリ。 +- 構造体のインライン格納と値渡し。 +- `int` の既定幅を現在の `i64` から `i32` に変えるかどうか。 -- `x++` は真(1)なので、`y++` を評価します。それも真です。 -- 続いて `z++` を評価します。これは偽(0)です。 -- `&&` はすべての条件が真であることを要求するため、最終結果 `g` は0になります。 -- 偽になるオペランドまでのすべてのオペランドを評価するため、`x`、`y`、`z` はすべてインクリメントされます。 +これらの言語変更には、それぞれ設計判断と導入する章の境界が必要です。 diff --git a/docs/roadmap.md b/docs/roadmap.md index aad2d9ca..75ee8d54 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,533 +1,287 @@ -# Compiler roadmap and design notebook - -This notebook preserves the design discussion from the former combined Chapter -25. Early sections include alternatives considered while implementing features -now taught in Chapters 25–28b; they are not all pending tasks. The -[stage audit](stage-audit.md) distinguishes current behavior from remaining gaps. -The [incremental-build design](incremental-builds.md) is a separate future item. -The final section records the outstanding class-initialization and compiler -follow-ups. Keep speculative language/library designs as proposals. - -## Separate Compilation +# Compiler roadmap English | [日本語](roadmap.ja.md) +Open compiler work and language proposals, reviewed against the current +snapshots on 2026-10-25. These are future changes, not promises about the +language implemented by the tutorial. Language changes still need their own +design decisions and chapter boundaries. -How to handle unknown forward ref? -No H-M, so no unification. -Demand no forward refs beyond functions... which will provide immediate signatures when def'd. - -Plan A: Remove FRefNode; fix all my bugs. -Fail A: I want mutual co-recurusion to work -Example: `val even = { int x -> x ? odd(x-1) : true; }; val odd = { int x -> x ? even(x-1) : false; };` - -Plan B: Require types ordered, but not any other fcn. `new T` can complain, -but not `fref.field` or `fref()` or `fref op fref` or `fref[]` - -Requires "lifting" Type.BOTTOM to the parser requirements everywhere. -So loading a field from an FRef requires: lift the FREF to a -TMP-to-TSTRUCT-with-FIELD... actually, just locally assume FREF will become -correct. Can't Lift it, no access, since type comes from random code. -TMP might be nullable (should error later). -TSTRUCT can/will be unnamed. Implies ALL TSTRUCT is unnamed. - -Issue is some unknown/remote/external/forward-ref. - -Allowing an FRef *at all* means everybody has to deal with seeing an FRef Type. -Allowing it for functions in same scope allow the above odd/even case . -Allowing it for structs in the same scope allows for co-recursive structs. -Promoting out-of-scope attempts the external lookup, then fails. - -Random junk usage: `sys.fld`. Looking up `sys` fails, now has to -guess: fref is ok, will be defined shortly... OR extern lookup now. -Then some usage delays trouble: -```java -// Extern or Future `sys` being returned. -val foo { -> return sys; } - -// foo() returns FRef, so Bottom, so the `.bar` has no type -// to do field lookups -val main { arg -> foo().bar } - -// HERE: Parser finds `sys` OR NOT?!?!?! -val sys??? = user_stuff....; - -``` - -Which gets to: parser never complains, builds SSA like a blind AST, -just wires up a graph with bad types all around. Requires: No Types -to guide parser! Just Syntax! - - (Obvious minor fail: widen floats- or-not) Fix: insert e.g. int-oper, return - type is MEET of Int,Float until both inputs are isa-Int and cannot lift to be - isa-Float - - Possible method call fail: - UNKNOWN_STRUCT_BOT.UNKNOWN_FIELD ( ARGS ); // Insert SELF OR NOT? - Possible fix: always assume a SELF arg, which is null for provable statics. - Optimization: constant parms get dropped from sig? *Constant* parms - must be constant from *Parser*, or else no unknown callers (so local opts). - Possible fix: SELF arg is *last* not *first*, easier to drop if dead? - - Load drops ConFldOff, keeps name. - Load keeps index unscaled, has index input, which is null for structs. - After Ptr Base is *closed*, base layout can happen, can fold index math. - - Load(ctrl, base, null ); // field name internal - - Load(ctrl, base, index_math ); // field name=='[]' - - Store(ctrl, base, null , val); - - FAIL: No Alias Info! - - -Plan C: - FRefs allowed for direct calls `odd()`, and for Types. - All other cases must *lift*. - - -## Unifying Structs and Arrays - -The idea is that structs have fields indexed by names, and arrays have fields -indexed by numbers, and these ideas are compatible with each other. This idea -has been used by the C language for a long time - an array can follow a normal -struct C and can index it with the usual array syntax. - -The alternative, using a field to hold a pointer to an array, exchanges some -flexibility for an extra indirection. The obvious flexibility allows swapping -the array out (generally for a larger one) and this is the basis of very common -`Vector` or `ArrayList` generic containers. - - -```java -struct str { - int hash; // A cache of the strings hash code - u32 #; // The array length - u8 []; // Array of bytes -}; - -str X = "abc"; -print(X.hash); // Read the hash field -X[0]=='a'; // Read array element 0 -X#; // Length of the array - -``` - -Here, the field named `#` holds the array length and the field named `[]` is -the actual array, which follows the other fields directly in memory. -Here is another example for variable length array: - -```java -struct vecInt { - u32 size; // Actual number of elements - u32 #; // The array length or capacity - int []; // Array of integers. -}; -``` - -The obvious limit here is that the array cannot be directly resized; it is -literally in the same memory block as the leading fields. Such a `vecInt` can -be *re-allocated* to a new larger `vecInt`, but the original `vecInt` will be -reclaimed - you cannot use the same `vecInt` reference forever. - -Any number of named fields are allowed: - -```java -struct Class { - !str !className; - !str !professor; - int time; - int credits; - !Class !prerequisite; - u16 #; // Limit of 65535 students per class - Student []; -}; -``` - -Or self-reference: - -```java -struct NTree { - Some val; - u8 #; // Length is 0-255 - NTree []; // More NTrees -}; -``` - -The syntax rules are: -- the last field is named `[]` and can be of any type. -- the second-to-last field is named `#` and must be some unsigned integer type - representing the maximum array length. -- may require a leading space in front of ` []` - - - -### Constructors and Arrays - -The allocation includes the array length, and can include a constructor: -`new NTree{val=17;}[2];` - -The constructor is optional, the array length is not. - -Optional function for initializing arrays, allows for write-once final arrays: -`new u8[buffer#,{ u32 idx -> buffer[idx].toUpperCase() }]` - - - -### Changing array max length - -An issue for large numbers of range-checked small arrays is that reserving a -full 4 bytes for array length can substantially increase the array overhead. -Simple allows for arrays to have smaller max lengths, although these are -treated as a different type than arrays with different max lengths. - -```java -struct x0 { u8 #; u8[]; }; // Array limited to 0-255 length -struct x1 { u16 #; u8[]; }; // Array limited to 0-65535 length -struct x2 { u32 #; u8[]; }; // -struct x3 { u64 #; u8[]; }; // - -x0 x = "abc"; // 4 bytes total: [3, 'a', 'b', 'c'] -x1 y = "def"; // 6 bytes total: [3, 0 , 'd', 'e', 'f', 0,]; 1 pad byte to acheieve 2-byte alignment -x = y; // ERROR, "x1 is not x0", no auto-widening -x += y; // OK, 7 bytes total: [6, 'a', 'b', 'c', 'd', 'e', 'f'] -y += x; // OK, 8 bytes total: [6, 0 , 'd', 'e', 'f', 'a', 'b', 'c'] - -x += "VeryLongString.... ....255_chars"; // Runtime RangeCheck - -// Explicit conversion to widen string -y = "" + x; -``` - - -### Two flavors of extendable arrays/strings: an extra indirection or not - -With an extra indirection you get e.g. Java `ArrayList` or C++ `Vector`. - -Without, then any call which extends the extendable array return a potentially -new object, only growing as needed. - -```java -val vec1 = new vecInt[0].add(2); // len=1, capacity=1 -val vec2 = vec1.add(3); // NEW !vecInt !returned, only is deleted; len=2; capacity=2; -vec1[0]; // ERROR, vec1 has been freed by call to `vec1.add(3)` -vec2[0]; // OK -vec2[1]; // OK -vec2[2]; // Runtime error, AIOOBE -``` - - -## A String Implementation - -Default strings have names like "str", "cstr", "ustr" or such. - -Always unicode8 with explicit u8 manipulations. Unicode "characters" will come -as function calls over the base implementation, probably in another string class. - -## Default string - -Efficiently growable via concantenation. Supports a variety of IO/printf functions. -The underlying array can be shared mutable if you ask for this. -The defaults will always be private mutable and read-only shared. - -```java -struct str { - - // Add a string to the string, possibly growing - val add = { str str -> - val self = this; - for( int i=0; i<str#; i++ ) - self = self.add(str[i]); - return self; - }; - - // Add an char to the string, possibly growing - val add = { u8 e -> - if( len >= # ) return copy(# ? #*2 : 1).add(e); - [size++] = e; - return this; - }; - - // Copy 'this' to a new larger size - val copy = { int sz2 -> - val v2 = new str[sz2]; - for( int i=0; i<len; i++ ) - v2[i] = [i]; - v2.len = len; - sys.libc.free(this); // Needs a better Mem Management solution - return v2; - }; - - val print { -> sys.libc.write(1/*stdout*/,[]/*array base as argument to C*/,len); return this; } - - u32 len; // In-use size - u32 #; // Max length is 4Gig, although variants can request smaller lengths - u8 []; // Character data -}; -``` - - -### A Final String +## Correctness -Intended to be a **immutable** string, possibly used as a base for a **secure** -string. This needs to be created all at once, and probably requires a -defensive copy. +### Narrow field results after method updates -```java -struct sstr { - // Possible static call syntax; no references to "this" - val make { str str -> new sstr[str#,str.at] }; - - // Array contents are immutable. - // Still figuring out good syntax for this. - u8 #; - u8 [~]; // Middlin '~'? Makes primitive array contents *immutable* and requires constructor syntax -}; +Chapters 25-27 can return an untruncated increment when a method updates a +narrow field and then reads it. With evaluator stores corrected, this example +returns 366 instead of 110. Chapter 25's generated RISC-V code also returns +366: the method's optimized Return uses the raw Add with range `[1-256]`, +although the Store writes a byte. Chapters 23, 24a/24b, and 28a/28b return 110 +with the corrected evaluator. -// Construction requires a function that produces the array contents. -// Here we are defining a defensive copy over `buf`. -sstr safe = new sstr[buf.len,{ int idx -> buf[idx]; }]; -sstr safe = sstr.make("abc"); // Defensive copy is made +```simple +struct C { u8 x=255; val next_noInline={ -> x++; return x; }; }; +var c=new C; +val a=c.next_noInline(); +val b=c.next_noInline(); +return a+10*b+100*c.x; ``` +Find where the field read loses its narrowing during parsing or optimization, +and preserve the converted result independently of the Store. Check both +signed and unsigned fields and compare ideal evaluation with generated code. -### A Extendable String (aka StringBuilder or StringBuffer) - -This API is intended to be a drop-in for a `StringBuilder` style, and takes an -extra indirection with each usage. - -```java -struct xstr { - !str !_str; // The extra indirection is here - val add = { str str -> _str = str.add2(str); }; - val write = { int fd -> _str.write(fd); }; -}; -``` - -### A Unicode String - -This API is intended to support unicode character manipulation over a plain `str` - -```java -struct ustr { - // Assuming idx is at the start of a character, return the character - // as a 32b integer. Returns junk integer if not at a character start. - // For all ASCII strings this will amount to a simple byte load. - // For bytes with high order bits set, this may read 1-4 characters. - val at = { int idx -> ... }; -}; -// Unicode character iterator -struct ucharator { -// Return successive unicode character codes -}; -``` - -### A "C" string - -A zero-terminated string, but the length has to be available directly - -```java -struct cstr { - u8 #; // length including the trailing zero - u8[]; // always ends in a zero byte; suitable for direct passing to C string functions -}; -``` - -### A "german string": a very short string that can exist in a register - -Length in memory is 1 byte (3 bits?) of length, then 0-7 chars; max 8 chars. -Fits in a 64 bit register. - -```java -struct gstr { // aka germanString, or blend string and register "streg" - u3 #; // Size is 0-7 bytes??? - u8 []; // Up to 7 bytes -} -// Using inlined object (see below). All these strings are short and are -// represented as packed integers in a 64b register. -*gstr prize1 = "gold"; -*gstr prize2 = "silver"; -*gstr prize3 = "bronze"; - -``` - -### Main becomes `val main = { str[] args -> ...}` - - -## Structs can be inlined - -Structs are normally by-reference, but can be converted to by-value by using -the `*` operator, pronounced "contents of" (this name might be changed to -e.g. `inline`). - -This is a name-space change only, you cannot take the address of the internal -struct without entirely consuming its lifetime. No "escaping". - -```java -// 2 64-bit words, no other overheads -struct Complex { f64 x,y; val len = { ->Math.sqrt(x*x+y*y); }; }; - -// Current Simple rules: always by-reference -struct ByRef { - !Complex !c; // 4-byte pointer to a Complex -}; -print(new ByRef.c.y); // Lookup requires 1 extra memory load from ref to c - -// New behavior: by value (inlining a struct) -// The '*' syntax indicator can be something else, e.g. a keyword "inline" -// I am pronouncing '*' as "contents of" -struct ByValue { - *!Complex !c; // c is inlined, full 16 bytes into ByValue -}; -print(new ByValue.c.y); // x,y inlined into ByValue, no extra memory load - -// Mixing Refs and Struct Values. Basically, a ref can be converted -// to a value by taking the "contents of" the ref, and values cannot -// be *converted* to a ref, although they can be copied over a ref. -ref. c = val.c; // Error, mixing refs and values -ref.*c = val.c; // Allowed, whole structure copy. Does not allocate, requires ref.c not-null -val. c = ref.c; // Error, mixing refs and values -val. c = *ref.c; // Allowed, whole structure copy -ref. c = ref.c; // Allowed, pointer copy -val. c = val.c; // Allowed, whole structure copy. -val *c = val.c; // Error, cannot take "contents of" a value - -// Alternative syntax, to avoid the "contents of" being on LHS and field applying to being on RHS: -ref.c = val.*c; // Assign into ref.c the "contents of" val.c - -// Arrays of inlined structures -var ary = new *Complex[99]; // Array of 99 !Complex !objects, inlined - -// Calling methods has the same syntax -ref.c.len(); -val.c.len(); - -// Function arguments use the same typing, so can pass-by-value -val math.sin = { *Complex c -> ... }; // Passes a complex by value -math.sin(ref.*c); // Requires de-reference to pass by value -math.sin(val. c); // Pass by value - -``` +## Compilation and optimization +### Reliable incremental builds +Chapter 28b now checks the reachable source-backed inputs before reusing saved +IR, including transitive dependencies, bundled sources, and missing inputs. +The remaining work is a build driver with recorded input identities, beyond +those conservative timestamp checks. An unchanged public signature does not +establish compatibility: optimization imports bodies and constants too. -## RoadMap for other chapters +Record the actual resolved input paths and versions, compiler/serialization +versions, target, ABI, and relevant options. Content hashes can cover preserved +timestamps and changed library search paths. Distinguish rebuildable source +from incompatible source-free libraries; their installation timestamps alone +cannot validate a set of objects compiled against one another. -`&&` and `||` -Drop `int` to `i32`. -Drop `TypeMemPtr` to 4 bytes; using `mmap` to make heap in low 4G. -Default parser for-loop construction, or partial peel. - `for( init; test; next ) body` -becomes: -```java -{ init; // Normal scope entry to bound lifetime of index variable -if( !test ) { // Zero-trip count test - do { - body; - next; - } while( test ); // Exit test at loop bottom by default -} }; -``` +An unchanged build should do no compilation or linking. Publish outputs only +after successful compilation, and relink when an object or other link input +changes. Start with a small build driver and conservative rebuilding of every +dependent; finer interface/body fingerprints can follow. The +[incremental-build design](../skills/incremental-builds.md) records the input +ownership rules and validation cases. -## `&&` and `||` +### Selective loading of library IR -The logical operators && (AND) and || (OR) are short-circuiting, meaning they may skip evaluating the second condition if the first one already determines the result: +Importing `sys.io.p` currently loads all ten units bundled in `sys.o`. +Independent, unrelated objects can remain unloaded, but selecting an object +reconstructs all its saved IR and follows its recorded dependencies. -- `&&` stops if the first operand is false. -- `||` stops if the first operand is true. +Design member-level IR loading and dependency discovery together. Splitting +objects alone is insufficient while reexports and unused function bodies +still pull in their dependencies. Preserve type identity, initialization rules, +and imported escape summaries. Loading, initializing, and emitting a class +are separate decisions. This optimization is independent of making +incremental rebuilding correct. -They are syntactic sugar—used for cleaner control flow—but they don't create new nodes in the execution model. +### Deterministic inlining -The `&&` and `||` constructs are simple syntactic sugars that only modify the control flow and do not -introduce new nodes. +The current inliner has a bounded exception for small initializer boilerplate, +but still uses parse-size estimates and worklist order. After shrinking +rewrites converge, choose one growing inline using current body sizes and a +deterministic ranking. Cache sizes per convergence round and use stable +call-site provenance to break ties, rather than newly allocated node IDs. +Avoid repeated whole-body scans and per-node dependencies while a large +function gradually shrinks. -Consider this example: -```java -int a = 1; -int b = 0; +### CFG frequency estimates -if(a++ || b++ ) { - if(b == 0 && a == 2) { - sys.io.p("Or"); - } -} else{ - sys.io.p("And"); -} -return 0; -``` -- `a++` evaluates to 1 (true), so `b++` is not evaluated. -- As a result, `b` stays 0, `a` becomes 2, and "Or" is printed. +After lowering and loop-tree construction, estimate block frequencies for GCM +and code layout. Treat initialization and known-failure paths as rare; use +bounded loop-trip estimates and branch probabilities when no profile is +available. Keep estimates consistent with control flow and shared between +scheduling and layout. These are backend estimates, not ideal-IR types. -```java -int a = 1; -int b = 1; +### Loop rotation -int x=1; -int y=1; -int z=0; +Consider lowering `for` loops with a zero-trip entry test and a bottom-tested +loop, or adding partial peeling. Preserve condition side effects, loop-local +scope, and the destinations of `break` and `continue`. The current parser +constructs a loop with its test at the head. -int g = x++ && y++ && z++; -... -``` -Here: -- `x++` is true (1), so it evaluates `y++`, which is also true. -- Then it evaluates `z++`, which is false (0). -- Since `&&` requires all conditions to be true, the final result `g` is 0. -- All operands up to the false one are evaluated, so `x`, `y`, and `z` are all incremented. +### Constant instance-field storage -## Class initialization and compiler follow-ups +When a final instance field has the same constant value in every instance, +fold accesses or store the value on the class object and omit the instance +slot. Preserve field-access semantics and public function reachability: +externally accessible function fields remain callable regardless of storage. +Existing static images for class constants do not implement this general +instance-layout optimization. + +## Language boundaries + +### Source privacy + +Underscores already select the public interface, but source access to another +class's private fields is not rejected. The proposal is to enforce privacy +starting in Chapter 28a, then preserve it across imports in 28b. This language +change still needs review; it is not a correction to impose on earlier chapters. + +- A field or method beginning with `_` is accessible only from its declaring + class, including constructors and methods. Enclosing, nested, and sibling + classes gain no special access. Same-class code may access another instance. +- A private class name is available in its declaring scope and inside the + class itself. Naming `App._Cache` must not grant access to `_Cache._data`. + Dotted lookup cannot bypass access rules, and private ancestors keep their + descendants out of the public interface. Distinguish naming a private class + from using a public value returned by a factory. +- Reads, writes, updates, method references, type names, and class-value + references must follow the same rule. Preserve source owner and location + through unresolved receiver types, inlining, and imports. Folding away a + Load or an unused result must not erase an illegal source access. +- Native declarations follow the same rules. Public wrappers may deliberately + expose private bindings; `sys` has no special exemption. + +The bundled `sys/io.smp` calls `libc._exit` directly. Keep that native binding +private, add a public `exit` wrapper in `sys/libc.smp`, and change IO to call +the wrapper. Bitset and array helpers access their own private fields and +remain legal. String spill fixtures with helpers outside `_String` accessing +`_hashCode` need explicit adaptation; do not grant file-wide access merely to +keep those fixtures unchanged. Validate native execution and imported source +ownership alongside the access diagnostics. + +### Constructor helpers and completion + +Local SSA field updates, including final fields, repeated assignments, and +loops, are implemented. The remaining question is broader: should constructors +be allowed to pass unfinished `self` to helpers that become safe after +inlining, and should completion checks wait until SCCP? + +Decide those rules before changing the current parser-time completion checks +and early-publication restrictions. Separate reading an initialized field of +an unfinished object from exposing that object to arbitrary code. The +construction/escape teaching boundary can then be reviewed against the chosen +semantics. + +### ELF visibility and unknown callers + +Escape analysis tracks functions reachable by unknown callers, while the ELF +writer exports retained named functions owned by the compilation unit. Decide +which symbols should be public, local, or hidden across separate compilations. +Keep source privacy, ownership for emission, and unknown-caller edges distinct; +keeping a body for linking must not imply arbitrary callers can reach it. + +## Initialization and runtime ### Entry arguments -Replace the integer initializer argument with an argument array. The selected -entry class receives a non-null array, empty when there are no arguments; -other class initializers receive null. Coordinate the parser, entry signature, -runtime driver, and separately compiled interfaces. +Replace the integer class-initializer argument with an argument array. The +selected entry class receives a non-null array, empty when there are no +arguments; other class initializers receive null. Coordinate the parser, +entry signature, runtime driver, and separately compiled interfaces. + +The native runtime already adapts process arguments for an explicit +`simple_main` function. That does not implement argument arrays for class +initializers. ### Pre-initialize classes from ELF data -Prove when a class's entire initialization can be represented by ELF static -data and relocations, with no remaining runtime effects. Emit its initial -contents and start its `$init` flag as true, so no initializer call is needed. -Carry that fact through separate compilation so importing compilers can remove -checks too. A constant field alone is not proof that the whole class can be -pre-initialized; I/O, argument-dependent work, and other observable effects -still require execution. Preserve any dynamic parent requirement, and do not -let cyclic dependencies justify their own pre-initialization. +Prove when a class's entire initialization can be represented by static data +and relocations, without remaining runtime effects. Emit those contents and +start its `$init` flag as true. Carry that fact through separate compilation +so importing compilers can remove checks too. -Mutable fields may have static initial contents without becoming constants. -Choose read-only storage only when no runtime writes remain. Static reference -cycles can use relocations without requiring a dynamic initialization cycle. +Constant final fields already have static images; that alone does not prove +the whole class can be pre-initialized. I/O and argument-dependent work still +require execution. Preserve dynamic parent requirements, and do not let cyclic +dependencies justify their own pre-initialization. Static reference cycles +can use relocations without requiring a dynamic initialization cycle. +Mutable fields may have static initial contents without becoming constants. For a wholly immutable, pre-initialized class, eliminate the mutable `$init` -state as well as the checks so the class image can reside in ROM. The native -function thunks preserve static initialization of function fields. +state as well as the checks so the class image can reside in ROM. Native +function thunks already preserve static initialization of function fields. ### Compact code and data arenas -Keep four-byte Simple references into bounded code and data arenas; the arenas -need not overlap or live near zero. Choose absolute addresses versus offsets -from an arena base when implementing placement and indirect calls. Native -function thunks can occupy the code arena while native libraries live elsewhere. -The current thunk implementation uses the existing low-address executable; +Use four-byte Simple references into bounded code and data arenas. The arenas +need not overlap or live near zero; choose absolute addresses or offsets from +an arena base when implementing placement and indirect calls. Native function +thunks can occupy the code arena while native libraries live elsewhere. +Currently function pointers occupy four bytes and use low-address thunks; ordinary data pointers still occupy eight bytes. Give full-width native pointers separate types and foreign lifetime contracts. -Opaque device addresses may need to be passed through without CPU dereference. -Simple object lifetimes should be managed automatically without a GC; -ownership, borrowing, cycles, and native retention still need design. Compact -storage alone does not establish those rules. Preserve static class images and -explicit native boundary conversions when choosing the arena representation. +Opaque device addresses may need to pass through without CPU dereference. +Automatic Simple object lifetimes without a GC still need a design for +ownership, borrowing, cycles, and native retention. Compact storage alone +does not establish those rules. Preserve static class images and explicit +native boundary conversions when choosing the representation. -### CFG frequency estimates +## Data and library proposals -After lowering and loop-tree construction, estimate block frequencies for GCM -and code layout. Treat initialization and known-failure paths as rare; use -bounded loop-trip estimates and branch probabilities when no profile is -available. Keep estimates consistent with control flow and shared between -scheduling and layout. These are backend estimates, not ideal-IR types. +These are language-design alternatives. The sketches below are not executable +examples of the current Simple grammar. -### Constant instance-field storage +### Structs with trailing arrays -When a final instance field has the same constant value in every instance, -fold accesses or store the value on the class object and omit the instance -slot. Preserve field-access semantics and public function reachability: -externally accessible function fields remain callable regardless of storage. +Allow named fields followed by an array length and inline element storage: + +```simple +struct vecInt { + u32 size; // Used elements + u32 #; // Capacity + int []; // Trailing elements in the same allocation +}; +``` + +The final field would be named `[]`, with an unsigned length field `#` +immediately before it. Element types could include references to the same +struct. Current arrays have a fixed `u32` length and a special two-field +layout; supporting arbitrary leading fields requires parser and layout work. + +Consider `u8`, `u16`, `u32`, or `u64` length fields to reduce small-array +overhead. Arrays with different length representations would be distinct +types, with explicit conversions and capacity checks. The spelling and +alignment rules remain to be decided. + +Growing such an object requires a replacement allocation. An extra level of +indirection, as in a growable-buffer wrapper, instead preserves the wrapper's +identity. Specify ownership before treating a growing call as consuming or +freeing its receiver. + +### Array initialization + +Allow an element-producing function at allocation, for example +`new u8[buffer#, { u32 idx -> buffer[idx] }]`. This would support final arrays +and fully initialized non-null reference arrays. Define when each element is +evaluated and when the array can be published. Existing zero-filled array +allocation does not provide this constructor operation. + +### String libraries + +Design a family of byte-based strings with UTF-8 decoding supplied by library +functions. Consider these distinct representations and interfaces: + +- A growable string with inline bytes and cached metadata, returning a + potentially new receiver when capacity increases. +- An immutable string constructed all at once, with defensive copying where + necessary. Read-only access alone does not establish exclusive ownership. +- A StringBuilder-style wrapper holding a replaceable pointer to its buffer. +- Unicode decoding and iteration over the byte representation, including + behavior at invalid character boundaries. +- A C-compatible string with a trailing zero and directly available length. +- A short string packed with its length into a machine word, passed by value. + +Keep mutable storage private by default and specify sharing, concatenation, +capacity growth, and native I/O contracts. Existing `sys` byte-array and +buffer helpers are a starting point, not this complete string design. + +### Struct values + +Allow a struct to be stored inline in another object, array, or argument list +rather than always through a reference. A proposed `*Complex` or `inline` +spelling would place both coordinates of `struct Complex { f64 x,y; };` +directly in the containing value. + +Define whole-value copying, reference-to-value conversion, and assignment +through an existing reference. A value must not silently become a reference +or expose an interior address with an invalid lifetime. Decide how consuming +an enclosing object's lifetime could permit such an address. Keep method +syntax uniform and define the calling convention for by-value arguments. + +### Default integer width + +Consider making `int` mean `i32` instead of the current `i64`. This changes +overflow behavior, layouts, APIs, and native interfaces, so it needs a language +decision and a deliberate compatibility boundary. + +### Parser Cleanup + +Get the Parsers' structure closer to an ideal R-D parser, and matching the +grammer doc. Move TYPES to CodeGen, and generally restructure for readability. +Ponder using try-w-resources to do the keep/unkeep pairing (do NOT do any +allocation in the `try`, just bring up an object w/auto-closable which tracks +the unkeeps) - see if that looks any cleaner. diff --git a/docs/stage-audit.md b/docs/stage-audit.md deleted file mode 100644 index dac2dc77..00000000 --- a/docs/stage-audit.md +++ /dev/null @@ -1,107 +0,0 @@ -# Chapter split cleanup audit - -This record distinguishes checked behavior, existing gaps, and future designs -while retiring the old combined Chapters 24/25. The final mapping is in the -[split record](chapter-split.md). Historical Java test names are retained as -regression identities; they do not identify current chapter boundaries. - -## Corrections in this cleanup - -- Chained comparisons now begin in Chapter 23. The early SCCP implementations - incorrectly compared a boolean intermediate with the third operand: - `10 < 30 < 20` returned true. Later implementations preserved the middle - operand but reused the first operator, making `0 < 2 <= 2` false. All current - snapshots from 23 onward now compare adjacent values using each written - operator. Regression cases cover both directions, strict/non-strict mixtures, - single evaluation, side effects, short-circuiting, and mixed-direction errors. -- Chapter 23's inherited, explicitly described fuzzer scratch contains many - comparisons using the former left-associative grammar. It is retained as an - ignored `testJigFuzzer`, consistent with the scratch-test policy; its mixed - directions are no longer valid source. Later snapshots already omitted it. -- Native-library and tuning material is preserved in [appendices](appendices/README.md), - and the former roadmap is now [docs/roadmap.md](roadmap.md). -- The recursive-return diagnostic already passed. It remains enabled in the - earlier snapshots and is enabled again in 28a/28b. The mutual-recursion test had an obsolete graph-string - expectation; it now checks its existing four execution results in 24b–28b. - In 24a, local SCCP cannot infer the recursive return type; the same example - checks that conservative diagnostic instead. This makes the boundary explicit. -- Allocation/runtime measurement runs passed after the comparison correction; - the changed spill goldens record the resulting smaller graphs. Historical - published tables retain their original compiler labels and dates. - -## Prefix-update follow-up - -Prefix `++` and `--` now use the same name lookup as ordinary reads and postfix -updates in Chapters 23 through 28b. A method's field goes through memory; a -constructor-local field stays in SSA. Previously, the prefix path updated the -field declaration's parser slot and silently lost the write. The two-call -reproducer now returns 21 instead of 11. Narrow update results stay alive while -Store folding drops a redundant conversion from its own input. - -`PrefixUpdateTest` covers increment/decrement, integer/float/byte fields, -multiple worklist seeds, local shadowing, final-field errors, native byte -wraparound, and module fields where supported. Existing final-constructor -coverage includes prefix updates in loops. - -The focused Linux checks pass in all eight affected chapters, including native -SystemV byte-wraparound execution and the existing final-constructor tests. -The early final-field rejection is retained so an illegal prefix update never -enters the optimizer as a Store; native data bindings still update their writable -contents despite having a fixed address. - -The Windows full suites cover 4,169 tests plus eight fuzzer regression runs; -shared printer and ISA checks pass. Focused reruns cover the final diagnostic -and prefix-result lifetime adjustments. The full Chapter 27 suite was rerun -after restricting the temporary keep to prefix results; its existing spill -expectations remain unchanged. No allocator goldens were updated. - -## Implemented, with narrower limits than earlier prose suggested - -- Separate compilation imports types and optimized IR without source parsing; - it reruns analysis and type checking with the new callers. -- Loading is by object and recorded dependencies, not individual used members. - A call to `sys.io.p` loads all ten bundled sys units. Independent, unrelated - objects can remain unloaded. Loading does not imply class initialization. -- Timestamp checks do not yet provide a reliable incremental build system. - The [design note](incremental-builds.md) records the missing pieces. -- Underscores select exports in modules; source privacy checks are still a - proposal awaiting review. Do not treat hidden exports as inaccessible source. -- Constructor-local final fields use SSA values and exit Stores. Reads of an - unfinished object are allowed only for initialized fields. Passing unfinished - self to arbitrary helpers and accepting completion only after SCCP are not - promised by the current chapter boundary. - -## Existing failures and deferred validation - -- The ideal evaluator does not truncate narrow stores in Chapter 23. A byte - update can therefore disagree with generated code; the postfix-only - reproducer is retained in the [queue](chapter-backports.md). -- A constructor loop with both break and continue still reproduces a worklist - assertion in Chapter 26. It is not caused by final-field mutability. -- `testModule0` is an ignored historical incremental-build experiment. It - assumes per-source output objects and unchanged explicit-input builds, neither - of which matches the present compiler's contract. -- Serialization's expensive byte-for-byte round-trip assertion remains disabled. - Ordinary import tests do not establish that stronger property. -- Direct probes of the remaining ignored tests found a stale forward-method - graph expectation and a missing `progs/sieve.smp` fixture in 28b. These and - the interactive stdin test remain ignored. Ignored Jig and - random-fuzzer entry points are intentional scratch/long-running tools. - Audit these separately before advertising their behavior as tested. - -Selective loading, full incremental builds, source privacy, compact pointers, -argument arrays, deterministic inlining, and broader constructor rules remain -future work. They are not prerequisites for completing the chapter numbering. - -## Validation - -The affected snapshots (23, 24a, 24b, 25, 26, 27, 28a, 28b) were checked with -their full Make suites on Windows and Linux, plus focused reruns after updating -test expectations. The final Windows coverage is 4,143 tests plus eight fuzzer -regression runs, with the shared printer and ISA tests also passing. The -native library/client cases and final Chapter 28b release build pass on both OSes. - -A local linear-history dry run produced all 31 tags in teaching order. The -final source snapshot, chapter navigation, Maven module IDs, and reviewed local -documentation links were checked. Measurement capture in 24b and dynamic -measurements in 24b/28b pass; the historical published tables were not rewritten. |
