summaryrefslogtreecommitdiff
path: root/chapter25/initialization.md
diff options
context:
space:
mode:
Diffstat (limited to 'chapter25/initialization.md')
-rw-r--r--chapter25/initialization.md842
1 files changed, 0 insertions, 842 deletions
diff --git a/chapter25/initialization.md b/chapter25/initialization.md
deleted file mode 100644
index 9996a3a3..00000000
--- a/chapter25/initialization.md
+++ /dev/null
@@ -1,842 +0,0 @@
-# Lazy class initialization design
-
-Design and implementation plan, revised 2026-10-10. Checkpoint 1 implements
-post-Opto initialization dependencies and cycle checking for review. Declared
-constant final fields already have static-data lowering. General lazy run-once
-initialization, serialized dependency validation, and initialization guards
-remain to be implemented; the compiler does not yet enforce the full protocol.
-
-Initialization metadata belongs with class identity and compilation-unit
-ownership, not solely on the `<clinit>` FunNode. Represent active touches in
-ideal IR, validate an acyclic graph across source and imported objects, and
-lower remaining checks to a hidden flag in the class object and a conditional
-call. The initializer itself owns the store that sets the flag.
-Use one runtime bit in the first, single-threaded implementation.
-
-## Language rules
-
-- Dynamic class initialization runs once, on the first active touch. A class
- never actively touched executes no initialization code.
-- A dynamic parent completes before a dynamic child starts. There is no early
- publication point or special tail-call escape from this rule.
-- Outside the initializer's own checked initialization context, no code may
- observe fields before their required initialization completes.
-- Class identity exists before initialization. Taking a class reference, as
- in `sys`'s `val io = io`, is passive and does not execute the child's body.
- Storing a function identity is also distinct from invoking it.
-- Proven static-data accesses require no dynamic work. This does not authorize
- a called function to observe unfinished dynamic state in an ancestor or any
- other class.
-- After Opto, build the combined function-call and class-initialization graph,
- including module boundaries. Reject an SCC only when it contains more than
- one `<clinit>`. Ordinary recursion, initializer self-edges, and helper cycles
- involving only one initializer are legal. Unresolved conditional branches
- contribute edges; eliminated branches and call targets do not.
-- Initialization order follows actual execution and parent requirements, not
- parser discovery order, worklist seed, or filesystem enumeration order.
-
-For example, A conditionally reading `B.foo`, with B initializing `foo` from
-`A.bar`, is rejected if that read survives Opto. Neither an initial zero nor an early evaluation of
-`A.bar` resolves that cycle. If B is A's child, the parent requirement already
-provides the reverse dependency.
-
-An initializer may read its own previously initialized fields under the existing
-definite-initialization rules. That is not an ordinary external active touch.
-Same-class helper calls are allowed by the cycle checker, whether or not they
-inline. Ordinary field-initialization and type checks remain separate.
-
-The cycle diagnostic lists every internal edge of the first rejected SCC,
-including helper calls. Every listed edge participates in a cycle; this reports
-all cyclic connections in that component without enumerating individual paths.
-On failure, rescan the component's functions to choose one surviving touch or
-call site for each edge. `SourceError.format(CompUnit, byteOffset, message)` prints
-`file:line:character: error: message` starting in column one, followed by the
-source line and a separate caret line. Lexer snapshots retain their
-original CompUnit through inlining; Call and CLInit serialization retain the
-offset and source identity/path. Imported source is read only on error. Parent
-requirements are labeled explicitly rather than assigned a fabricated position.
-This is a focused diagnostic utility, not a migration of all parser errors.
-
-## Ownership and identity
-
-Keep `CompUnit._deps` for source/object discovery and rebuild dependencies.
-Do not reuse it as the initialization graph: passive references belong there,
-and its elements are files rather than necessarily individual classes.
-
-Use CompUnit directly for the current runtime initialization graph. The parser
-creates a file `<clinit>` in `parseStruct(true, ...)`; nested struct declarations
-use `parseStruct(false, ...)` for instance initialization. Do not introduce a
-separate ClassInit object or invent a dynamic class initializer for those
-instance constructors.
-
-Reuse `_cname` for stable identity, `_par` for the filesystem parent, `_clz` for
-the canonical class object and hidden field, and existing function identities
-for the body. Add only initialization-specific information:
-
-| Member | Meaning |
-|---|---|
-| `_classInitDeps` | Lazily allocated `Ary<CompUnit>` of direct initialization requirements |
-| `_initComplete` | All initializer touches and relevant call targets have resolved |
-| `_initStatic` | Proven that this unit's own initializer needs no execution |
-
-Keep diagnostic witnesses in the checker's temporary function summaries: source
-locations and indirect call witnesses where applicable. CompUnit retains only
-the edges and analysis flags after validation. Derive the flag alias from `_clz`'s
-`$init` field rather than maintaining a duplicate mapping. Locate the body by
-its stable function identity; a transient FunNode cache may be useful but does
-not define class identity. An unloaded or eliminated FunNode is not proof of
-static initialization.
-
-Static class objects for nested struct declarations keep their existing named
-TypeStruct identity and defining CompUnit. Their materialized data need no new
-runtime graph vertex; an active use that requires the enclosing file initialized
-maps to its CompUnit. Preserve initialization-independent static accesses. If a
-future language feature adds an independent dynamic initializer inside one file,
-then introduce the additional per-class graph identity that feature needs.
-
-`CompUnit.addClassInitDep(code, target)` allocates the collection lazily, deduplicates
-edges, and invalidates a compilation-wide validation stamp. Keep self-edges so
-they can be diagnosed. Explicit namespace-only parents have no dynamic body;
-a missing parent definition must not silently become an empty initializer.
-
-There are two different dataflows. Runtime `$init` values use ordinary memory
-aliases and support flag-load/CLInitNode optimization. Dependency discovery
-records which initializers may be requested, independently of incoming flag
-values and normal return. A dedicated analysis-only memory slice could encode
-that second forward dataflow, but the first implementation uses explicit
-CompUnit edges plus small function summaries. It does not add a second family
-of memory aliases, and it does not infer cycles from runtime `$init` types.
-
-## Discovering and validating dependencies
-
-Use the convention `A -> B` to mean that initializing A may require B completed.
-Topological output therefore places prerequisites before dependents. A dynamic
-child has a structural dependency on its parent. An active touch of another
-class during A's initializer contributes an edge; a passive reference does not.
-
-Insert active touches as CLInitNodes on executable control. After Opto finishes
-SCCP, inlining, and cleanup, collect surviving touches and calls. Use final
-function-pointer target sets and propagate initialization effects through the
-resulting call graph with a worklist. Recursive function SCCs reach a fixed
-point of finite dependency sets; they are not themselves initialization cycles.
-Indirect calls contribute the union of targets still possible after optimization.
-
-Do not accumulate historical targets or source touches eliminated by Opto.
-There is no side table watching Node replacements or type changes. An inlined
-helper's surviving CLInitNodes belong to its caller; an uninlined helper's
-summary propagates through its surviving calls. Walk executable control
-forwards so touches before nonreturning calls and infinite loops are included.
-Preserve initializer boundaries until validation; expanding/inlining CLInit
-operations early must not erase the class identity needed for checking.
-
-A field value becoming constant does not prove its initializer unnecessary.
-CLInitNode survives independently of the load. Dead control may remove both;
-a static-initialization proof may remove the obligation. Such proofs must not
-assume the very acyclicity being checked.
-
-Acceptance now depends on what Opto proves. Worklist determinism remains an
-implementation goal, but preserving every intermediate target set is not the
-language rule. Before declaring a graph complete, every surviving required
-initialization-time call must have a complete effect summary.
-
-An unknown function-pointer target or native callback is not an empty summary.
-Reject such a call during initialization unless an explicit trusted contract
-establishes its initialization effects, including that it cannot call back into
-unfinished Simple initialization. Ordinary native I/O can use such a contract;
-this is independent of whether the call has memory or I/O effects.
-
-Proving a class entirely static is a separate, deterministic classification
-step. Static object/reference cycles are legal and need no dynamic ordering.
-Dropping a dynamic requirement requires proof that the relevant operation needs
-no initialization, not merely that its guard or result optimized away. A real
-cycle must not bootstrap its own static-ready proof.
-
-Validate the union of parent edges and active initialization requirements after
-the source/import closure is resolved and again after loading or replacing
-summaries. Report a cycle with class names and the source/call witnesses for
-its edges. An incomplete library may retain explicitly unresolved obligations;
-it cannot be certified as a complete runnable program.
-
-Use a stable name order to break ties in diagnostic/topological output. Cache
-module-local results, but validate cross-module edges at final composition.
-One module's list is not necessarily a contiguous block: `X.A -> Y.B -> X.C`
-in prerequisite-first order requires interleaving X and Y. Retain edges as the
-authority rather than serializing a local order as a global proof.
-
-The topological order validates permitted completion order; it is not a startup
-schedule. If A conditionally uses independent B, B initializes only on the
-taken path. A may start before B and finish after it. That is safe precisely
-because B cannot directly or indirectly require unfinished A. Do not eagerly
-initialize every possible dependency, or a prefix of the topological list.
-
-## Ideal IR and runtime protocol
-
-### Compiler phase boundaries
-
-The Chapter 25 driver runs `Parse -> Iter -> Opto -> IterPostOpto -> FinalizeIR
--> TypeCheck -> LoopTree`, followed by the existing serialization and backend
-phases. `Opto` performs SCCP, resolves numeric modes, and seeds the worklist;
-it does not invoke either Iter pass. `IterPostOpto` performs peepholes and
-inlining using the SCCP results. `FinalizeIR` resolves field layouts and offsets,
-runs the resulting cleanup, roots surviving functions, and releases temporary
-CU memory keeps. Numeric resolution remains in Opto because it can require
-another SCCP propagation before the types are ready for Iter.
-
-Direct callers explicitly run `parse().iter().opto().iterPostOpto().finalizeIR()`
-before `typeCheck()`. The driver handles that sequence when a later phase is
-requested. CLInit expansion after successful TypeCheck remains unimplemented;
-its phase and the serialization boundary are separate decisions.
-
-### Memory IR checkpoint (2026-10-17)
-
-This checkpoint supersedes the older representation details below. Keep CLInit
-opaque through Iter/Opto and the class-cycle check; introduce neither its
-conditional call CFG nor frequency estimates in those phases.
-
-Use the existing mutable `$init` field and alias. The implemented node shape is:
-
-```text
-ready = Load(C.$init, incoming memory)
-CLInit(ctrl, wholeMemory, classPtr, ready)
- -> (ctrl, wholeMemory, initAliasMemory)
-```
-
-The third result is an alias slice containing true on normal continuation.
-Merge it over the whole-memory result using MemMerge. The whole-memory result
-conservatively accounts for arbitrary initializer effects; changing just the
-flag slice would be incorrect. The flag result is a fact about the operation's
-result, not an extra runtime store at each touch. Keep the class pointer and
-source location on the operation; resolve the flag alias from the class type,
-including after forward-reference resolution and object loading.
-
-The parser creates the ready load up front, using the existing receiver node.
-For an unresolved receiver, ordinary Load/FldOff machinery resolves the alias
-and offset later; no additional named FRef is needed. If the receiver becomes
-an ordinary instance, the speculative CLInit and flag load disappear together.
-If the parser cannot yet identify the flag alias, precise Loads and MemPhis
-recover that slice from the whole-memory projection once the type resolves.
-
-Known-true input bypasses the operation, returning incoming control/memory;
-false or unknown retains it. Parser memory uses the result, so subsequent flag
-loads, MemPhis, and ordinary peepholes can establish redundant checks without
-a separate parser set of initialized classes. A merge establishes true only
-when every live incoming path does. Initially an ordinary unknown call may
-lose a known-true fact; that is conservative, and preserving monotone flag
-facts across arbitrary calls can follow later.
-
-The static flag starts false. Its true store belongs inside `<clinit>`, not at
-each CLInit site. It is installed at body entry so the body knows its own flag
-is true. The parent requirement remains structural; runtime lowering must
-execute it before entering the body. This implements the accepted same-class
-reentry rule; it does not exempt cross-class cycles from validation. Retain
-initializer boundaries and their dependency edges until the post-Opto check;
-do not propagate an in-progress caller's flag into another initializer to
-erase that check. Ordinary field-initialization checking remains separate.
-
-Memory/projection wiring, parser updates, the initializer-owned flag store,
-and copying/serialization are implemented. Rebuild serialized objects for the
-new four-input node shape. Runtime lowering, entry adaptation, and executable
-run-once behavior remain a subsequent step.
-
-Public function fields on instances of public classes must retain their Start
-caller, just like public class-object fields. `CodeGen.publicFIDX` now checks
-both, preserving private-field/class and hidden-constructor exclusions. This
-fixes the `sys.scan.match` widening exposed by conservative CLInit memory
-effects; neither imported-type resets nor SCCP assertions needed changes.
-
-Validation: 395 raw0 tests, 37 raw1 tests, 9 standalone tests, the fuzzer
-regression wrapper, 25 Chapter 25 tests, and 38 additional tests all pass.
-The additional group includes 16 class-init tests
-covering cycles, serialization, repeated touches, branch merges, flag stores,
-and removal of a speculative flag load when a forward call returns an instance.
-
-### Deferred constant instance fields
-
-A final instance field proven to have the same constant value in every instance
-can eventually be stored on the class object, or folded directly to a
-ConstantNode, and removed from instance layout. Instance-field access semantics
-remain unchanged. Public function reachability must not depend on that storage
-choice: an externally accessible function field is callable by anyone whether
-stored in the instance or class. This optimization is not needed for the current
-initialization checkpoint; instance layout is unchanged.
-
-### Deferred CFG frequencies
-
-After Opto and cycle validation, lower surviving CLInit operations to flag
-tests, conditional calls, and control/memory merges. Mark the needs-init edge
-rare. Once the resulting CFG and loop tree are stable, estimate frequencies
-before GCM and reuse them in Encoding block layout. Frequencies are derived
-backend data, not ideal-IR types or inputs to Iter/Opto.
-
-Start each function at 1.0; use 50/50 for unknown tests, 90/10 for non-null/null,
-and a small positive minimum probability for initialization and known-failure
-edges. Use an estimated loop trip count of 10 unless a proven count is known;
-distinguish body iterations from header tests and account for early exits.
-Compute cyclic flow using loop-aware propagation, not an unbounded walk around
-backedges. Regions sum incoming edge flows, and branch outputs sum to their
-input. Bound probabilities and loop amplification and normalize outgoing
-probabilities; do not independently clamp successor counts and break those
-relationships. Unreachable blocks remain zero. Bound the resulting estimates
-against overflow. Later profiling can supply probabilities/trip estimates
-through the same interface; switch distributions are deferred.
-
-GCM chooses the lowest-frequency legal placement, with deterministic tie breaks.
-Encoding prefers common fall-through paths, forward cold branches, and backward
-loop edges. Recompute/invalidate estimates if later CFG changes require it.
-
-Introduce an effectful `CLInitNode` with a stable target CompUnit identity:
-
-```text
-CLInit(C, ctrl, memory) -> (ctrl, memory)
-```
-
-Control is input/output slot 0 and memory slot 1. Store the target class as
-semantic metadata or a separate constant input, never in the control/memory
-slots. Include target identity in equality, copying, printing, and serialization.
-Its normal return guarantees the required initialization of C and its ancestors.
-It can execute arbitrary initializer effects, so it is not a pure flag test.
-
-At dynamic class-field reads and writes, required instance creation, and calls
-requiring initialized class state, consult parser flow facts first and insert
-CLInitNode only if the required initialization is not already established.
-A check omitted by a valid dominating initialization proof needs no separate
-historical dependency record: its prerequisite is represented on the covering
-path. Passive Simple class references remain subject to checks at later field accesses.
-Native escape of class references is a separate boundary described below.
-Forward references must preserve the pending check until the target is known.
-
-For `ary.copy(args)`, check the class at the field access:
-
-```text
-CLInit(ary)
-f = load ary.copy
-call f(args)
-```
-
-The ordinary function header, calling convention, and static function-pointer
-field do not change. There is no per-function checked wrapper. Folding the
-field load to a constant function pointer does not remove the preceding
-initialization effect unless that effect is independently redundant. Once the
-checked load yields `f`, it can be passed along and called without repeating
-the check for `ary`. Materializing a function address in static data is not a
-source-level field access. An initializer's raw body remains private compiler
-machinery, not a callable reset operation.
-
-Keep CLInitNode in ideal IR so its peepholes can eliminate or simplify frequent
-checks. Lower a remaining node to ordinary Load/If/Call/Region/Phi IR before
-machine selection, using a shared class-initialization helper where an
-out-of-line guard is useful. The checking code contains no flag store:
-
-```text
-CLInit_C(ctrl, memory):
- (ctrl, memory) = CLInit_parent_if_required(ctrl, memory)
- if load(C.$init, memory):
- return (ctrl, memory)
- return call C.<clinit>(ctrl, memory)
-
-C.<clinit>(ctrl, memory):
- memory = store(C.$init, true, memory)
- ... original initializer body ...
- return (ctrl, memory)
-```
-
-Entry to `<clinit>` already means that its check took the not-initialized path.
-Place the flag store in the initializer, naturally at its entry. This covers
-all its normal exits without replicating a store in each CLInitNode expansion.
-Inlining the body may of course copy its store, just as it copies other body
-instructions. The body must not guard itself through ordinary method-entry
-instrumentation.
-
-There is no requirement to schedule the store last. With a validated acyclic
-initialization graph and one thread, no other user can observe this class's
-flag while its initializer is running. A nonreturning initializer can leave
-the bit set because no permitted observer can resume. The physical flag store
-is not an early publication point: the class's initialization still completes
-only when its body returns. Cycle validation and definite-initialization checks
-must remain independent of that store and of optimizations which expose it.
-
-The first runtime contract is single-threaded initialization, with no recovery
-and retry after initializer failure. The validated DAG rules out recursive
-entry to an unfinished initializer. Thus no runtime `initializing` state is
-needed. Concurrent first touches or recoverable initialization failures require
-an extended protocol; the flag is not a synchronization mechanism.
-
-## Hidden flag and known completion
-
-Add a hidden boolean field `$init` to the ordinary `<clinit>` class object.
-There are few such objects, one per class; no separate allocation, symbol, or
-special bit-packing scheme is needed. Use the existing small-integer field
-representation and normal struct layout/offset machinery. It is a logical bit,
-initially false for a dynamic body, stored true by that body, and inaccessible
-to user writes. Separate importers share it through the existing `C.$class`
-identity and its canonical field layout.
-
-Allocate/remap these aliases during class discovery, before the current
-`freezePublicInterface()` boundary. Hidden flags are not source-visible fields,
-but their memory effects must participate in call summaries and memory merging.
-Keep CLInitNode's complete public-memory effect until a narrower summary is
-proved; tracking only the flag would lose the initializer's field and I/O effects.
-
-There are two different facts:
-
-1. **Static body completion:** `_initStatic` proves that C's own initial contents
- are supplied by data and there are no remaining initializer effects. Its
- `$init` field starts true, and its redundant body/store can disappear.
-2. **Completion at a program point:** normal return from CLInitNode establishes
- readiness in that control/memory state. At a permitted external touch, a
- true flag branch proves completion of C's own body; its ancestors must also
- be known complete before concluding that an ordinary active use is safe.
-
-Inside C's own initializer, the flag can already be true while fields are still
-being initialized. Keep that lexical initialization context and its field-read
-checks; a folded true flag does not grant access to unfinished fields. In
-particular, it must not make a callback or cyclic initialization dependency
-legal. Record and validate those dependencies before suppressing their checks.
-
-Neither fact means mutable fields remain constant. Do not refine their declared
-types to their static initial values. Also, a static-only child with a dynamic
-ancestor is not globally ready for every active use: ordinary ensures still
-establish the ancestor. Passive references and proven initialization-independent
-static accesses need no such ensure, including during the parent's initializer.
-
-## Parser flow and ideal peepholes
-
-CLInitNode will occur frequently. Avoid constructing obvious duplicates in the
-parser, then use normal ideal optimization as more types, call effects, and
-control-flow facts become available. A separate late must-analysis is optional,
-not a prerequisite for removing straightforward redundant checks.
-
-Carry known-initialized class identities with parser control flow, for example
-as a BitSet in ScopeNode keyed by the existing remapped `$init` alias IDs. These
-are path facts, not mutable global properties of CompUnit or its declared
-TypeStruct:
-
-- On an active touch, consult the facts. If C and its required ancestors are
- known ready, omit the CLInitNode. Otherwise insert the obligation in the IR;
- post-Opto analysis collects surviving requirements.
-- Normal return from CLInit(C) adds C and its required ancestors. Do not add
- every possible dependency: a conditional initializer may never touch one.
-- Copy facts on a branch and intersect them at a reachable merge. An abrupt
- or nonreturning path contributes no completion facts to a reachable merge.
-- A loop's first iteration has only entry-path guarantees. Body/backedge facts
- alone cannot suppress its first check or establish readiness after a
- potentially zero-trip loop. Preserve pre-loop facts and refine only with a
- proof covering all relevant incoming paths.
-- A call cannot reset established facts. Add new ones only from a proven normal
- return summary; do not seed arbitrary exported function entries with facts
- learned at one caller. Checks at field-access sites establish their facts.
-
-Give CLInitNode ideal peepholes for these cases:
-
-| Proof | Rewrite |
-|---|---|
-| Fully static class / flag known true at a permitted touch | Remove the class's own check and call; retain any still-required ancestor initialization |
-| Flag known false at this memory/control point | Replace the test with an unconditional initializer call; retain the call's effects and results |
-| Earlier normally returning CLInit of the same class dominates | Forward control/memory through the redundant node |
-| Dominating true flag test covers this path | Remove the covered check, as with redundant if-test reasoning |
-| All reachable arms of a merge establish completion | Fold the repeated check after the merge |
-
-Known false must come from the memory reaching the node, not simply the false
-byte in the original object image. Known true on one branch is not known true
-after a merge with an unchecked branch. Match stable class identity and required
-ancestors, not just a pointer-shaped value or a similar field name.
-
-Use the existing dependency/wakeup mechanism when a node consults a flag value,
-memory state, or another node's proof, so later refinement revisits the check.
-Retain control and whole-memory ordering through the conditional call: removing
-a check must preserve preceding initialization effects, and hoisting a check
-onto an untaken path would violate laziness. In particular, CLInitNode is not
-freely speculatable or an ordinary pure expression suitable for unrestricted GVN.
-
-Parser omission must have an enduring IR justification: earlier effectful
-CLInit/control edges, a proven caller precondition, or static metadata.
-When bodies are inlined or deserialized, those proofs travel with the IR;
-transient parser facts alone cannot justify an unchecked exported body. A
-shared declared type must not be mutated to encode a path-local true flag.
-
-## Static data classification
-
-A class is fully static only when every required initial value is representable
-in data/relocations and no runtime effect remains: no I/O, observable allocation,
-potential failure, or required dynamic dependency. A missing/dead body alone
-does not prove this, and startup arguments are not static inputs.
-
-Ignore the compiler-inserted `$init = true` bookkeeping store when checking
-whether the original initializer is entirely static. Otherwise every inserted
-store would prevent this optimization. Once the proof succeeds, emit `$init`
-as true in the object image and eliminate the runtime store with the body.
-
-Mutable fields may have static initial values. Folding their initializing
-stores must still preserve reads and escapes during the initializer. Merely
-knowing the final value and that the body runs once is insufficient. Preserve
-object identity and canonical layout separately from initial contents, and do
-not collapse distinct class objects with equal bytes.
-
-This extends the current StaticData machinery; it does not change pointer
-widths or require class objects to become read-only. An object with a dynamic
-`$init` field stays writable even if its source fields are final. A fully static
-object whose source fields are also immutable can later use read-only storage,
-with its hidden field already true and no runtime writes to it.
-
-## Serialization and final composition
-
-Add a class-initialization directory to the `.simple` data that can be read
-without materializing every function body. Serialize:
-
-- Stable class and parent names, defining-unit identity, and initializer symbols.
-- Static/dynamic/unknown classification and summary-completeness information.
-- Direct initialization requirements and function-effect summaries, including
- unresolved obligations and useful cycle-diagnostic locations.
-- The hidden `$init` field's type, alias identity, initial image value, and
- canonical layout, alongside the other class fields. Remap aliases using file
- identity and local order rather than copying process-local numbers.
-- CLInitNode operations and the initializer's own flag Store in ideal IR,
- including all semantic fields needed to rebuild them.
-
-Do not serialize transient node references, a cached global topo index, or the
-runtime value of a dynamic flag as an unconditional type fact. A definition
-and its imported declarations must resolve to the same owning CompUnit and
-canonical class/flag identity.
-
-On load, register identities first, resolve parent/dependency edges second,
-then resolve function summaries and IR. Check conflicting owners, missing
-definitions, alias remapping, summary completeness, and combined cycles.
-Loaded bodies must agree with their summaries; report newly found requirements
-and revalidate rather than trusting an earlier module-local topo sort.
-
-Preserve these summaries before `unlinkImports()` removes imported bodies.
-Serialize ideal CLInitNode operations before lowering to machine code. Native
-objects retain checks at their class-field access sites even when a particular
-importing compiler inlines and removes its local checks. Ordinary function
-symbols and function-pointer constants are not replaced by checked wrappers.
-
-Do not serialize a parser's transient BitSet as unconditional entry knowledge.
-Its local alias IDs can change on reload; retained checks, control flow, and
-explicit entry contracts must support any omitted checks. Recompute flow facts
-for loaded graphs and verify hidden-field ownership/layout and that the flag
-store belongs to `<clinit>`, not to each checking caller.
-
-Final validation needs a Simple-aware executable/library composition step.
-An ordinary native linker cannot validate dependencies stored only in `.simple`.
-Enumerate the complete intended Simple object set, including exported entry
-points that native code could invoke; loading only the source-discovered
-references is insufficient. Validate that metadata set before handing objects
-to GCC/the native linker. Dynamically loading additional Simple modules is
-outside this first protocol and would require validation before activation.
-
-Keep the current unversioned `C0DE` convention and require rebuilding objects
-when the serialized layout changes. Retain/validate summaries for the exact
-objects being linked, not cached descriptions of older builds.
-
-## Program entry and other boundaries
-
-Program startup is another CLInitNode on the starting class. In a fresh runtime
-image with a dynamic starting class, its `$init` is known false, so the check
-folds to the initial `<clinit>` call. The starting class sets its own bit just
-like every other class; there is no adapter-specific flag store or protocol.
-A proven fully static starting class needs no dynamic initialization call.
-
-The current file `<clinit>` can receive `arg` and return a program/test result.
-Preserve those arguments and the call result on the known-first-call startup
-path. Ordinary class-use checks need only control and memory; they do not need
-to cache or invent a result for the skip path. For fully static startup, the
-program result must also be available as a constant or retained computation;
-static object contents alone do not prove the entry computation unnecessary.
-Existing tests executing file code with different arguments use independent
-runtime images, not a reset of an already initialized class. The startup fact
-must not be reused for an arbitrary native entry into a live image.
-
-Own-initializer helper calls and construction of a special instance during
-class initialization need an explicit definite-initialization proof. Do not
-carry forward the old module notes' unrestricted “golden instance” exception.
-Initially reject cases that would publish or inspect unfinished class state.
-
-## Native handoff
-
-Native callers are not required to understand or explicitly run Simple class
-initialization. The compiler/runtime must establish initialization before
-exposing a supported value, or reject an unsupported handoff.
-
-For the first implementation, reject passing a class object or raw `<clinit>`
-function to native code, including through a class-to-address conversion.
-Do not confuse these with an ordinary function pointer obtained by a checked
-field access: loading `ary.copy` already initializes `ary`, so that value does
-not need a new initialization wrapper when handed to native code, wherever the
-ordinary FFI supports its signature. Initialization-time callbacks still need
-the effect analysis described above; an uninitialized class cannot be hidden
-behind a callback to evade cycle validation.
-
-Later, support a selected class value at the handoff point:
-
-```text
-X = rand ? B : C // Passive class references
-CLInit(selected class X) // Initialize the actual value being passed
-libc.native(X)
-```
-
-Validate the initialization graph for all possible targets, B and C, including
-their parents and transitive requirements, regardless of the selected value.
-At runtime initialize only the selected target before the native call. This
-can lower to checks on the corresponding control-flow arms, or dispatch on a
-known target identity. The plan's initial CLInitNode has a fixed class identity;
-this dynamic selection is a later extension, not an assumed existing facility.
-
-Initializing every possible target before handoff is an alternative policy,
-but it runs effects for the unselected class. Prefer the selected-target form
-to retain the rule that untouched classes run no code. Unknown target sets or
-native traversal to further uninitialized class objects require additional
-analysis; initializing just X is not a blanket proof for everything reachable
-through X. Until supported, reject the handoff rather than transferring that
-responsibility to native code.
-
-A native call directly to an exported symbol, without a preceding Simple
-handoff, likewise needs compiler/runtime initialization support. Its eventual
-export/startup mechanism is deferred; it must not require the native caller to
-invoke `<clinit>`. Existing native-linking tests are not evidence that this
-future initialization guarantee is already implemented.
-
-## Implementation sequence
-
-Implement in Chapter 25 only, with the following reviewable checkpoints. Keep
-existing source/native entry conventions and pointer widths. Each checkpoint
-must build; do not claim the runtime guarantee until the executable protocol,
-serialization, and final-composition validation are all present.
-
-### 1. CompUnit dependencies and cycle checking
-
-Reviewed (2026-10-10). Files: `CompUnit.java`, `ParseAll.java`,
-`Parser.java`, `ClassInitDependencies.java`, `CLInitNode.java`, `CallEndNode.java`,
-`Opto.java`, and `CodeGen.java`. Generic `Node` needs no changes.
-
-- CompUnit holds direct `_classInitDeps`, `_initComplete`,
- and reserved `_initStatic`. Existing file-discovery `_deps` is unchanged.
- Diagnostic witnesses belong to the dependency checker and serialized summaries,
- not CompUnit.
-- Parser inserts a control-flow CLInitNode for an active class-field touch or
- a receiver whose class is not yet resolved. Ordinary instance receivers
- fold the obligation away when resolved. Passive references and direct
- own-initializer field accesses do not insert a check.
-- This checkpoint's ideal node has control at input 0 and receiver at input 1;
- it neither consumes nor produces memory yet. The full runtime form described
- above will add memory at slot 1 and move the receiver to slot 2. No hidden
- flag or runtime initialization call is generated in this checkpoint.
-- After Opto, the collector walks live control forwards in each surviving
- function, reads final call-target sets, and propagates sparse may-touch
- summaries. Ordinary helper inlining naturally relocates its checks into the
- caller. Class initializers are not inlined before validation.
-- Reachable nonreturning calls retain a conservative CallEnd during dataflow.
- Calls left out-of-line retain their continuations, so otherwise unreachable
- touches after them can contribute initialization dependencies. This extra
- ordering is accepted; users can remove such unreachable code.
- Inlining discovers bodies forwards from their entry, including non-exiting
- paths, and explicitly retains the Return node even when disconnected from
- executable control. Nonreturning functions can therefore inline normally.
-- TypeCheck builds fresh CompUnit requirements from those summaries and parent
- edges. It rejects cycles with source/call witnesses and computes a stable
- prerequisite-first order. No source-history or Node watcher table is kept.
-- Following validation and serialization, the temporary CLInit obligations are
- erased for the existing backend. Executable lowering remains a later
- checkpoint. The full lazy-initialization runtime guarantee is not implemented.
-
-Unknown native effects and unresolved targets leave `_initComplete`
-false; imported summaries are now available in checkpoint 2. The returned order
-covers known edges and is not a completeness
-certificate. `_initStatic` remains false until static classification is added.
-
-Validation covers direct, helper-mediated, forward, conditional, indirect,
-sibling, and parent/child cycles; legal ordinary recursion and passive
-references; unknown native/imported effects; dependencies eliminated by SCCP;
-unused helpers; discarded field values; and effects before versus after a
-nonreturning call. Cycle and optimized-target cases run with three worklist
-seeds. A bounded Eval2 run verifies that a retained nonreturning call executes
-rather than falling through; its x86, ARM, and RISC-V encoding checks also pass.
-All 492 tests in the parallel Chapter 25 Make suite pass. The encoding checks
-were added afterward and pass in the focused suite. Two existing RISC-V spill
-expectations change from 9 to 7; execution and allocation checks pass.
-Generated fixtures are under
-`chapter25/build/init-deps/`; full-suite logs are under `build/init-deps-opto*.log`.
-
-### 2. Serializable flags and dependency summaries
-
-Implemented for review (2026-10-10). Files: `Parser.java`, `Serialize.java`,
-`ElfReader.java`, `ParseAll.java`, `ClassInitDependencies.java`, `CodeGen.java`,
-and `CLInitNode.java`. Existing `GlobalBits` and `StaticData` mechanisms handle
-alias/function remapping and the zero flag image without special cases.
-
-- Add hidden `$init` fields with the normal field/alias/layout mechanism before
- `freezePublicInterface()`. Declared flag type is boolean; initial image value
- is false unless static initialization has been proved. Do not make a dynamic
- flag's declared type the constant false.
-- Serialize per-unit initialization metadata and function may-touch summaries
- in `.simple`, early enough for graph validation without loading every body.
- Preserve stable owner/alias/function identities and diagnostic witnesses.
-- Register imported units first, resolve/remap edges and hidden fields second,
- then validate the combined graph. Neither a module-local topological order
- nor a summary from an older object file is a final-link proof.
-- Keep metadata for fully static or body-eliminated units. Rebuild objects
- under the existing unversioned `C0DE` convention; add no compatibility layer.
-
-Checkpoint: a two-module cycle rejected from source is also rejected after
-serialization; an acyclic diamond resolves to one shared class and flag.
-Two local topo orders can be merged even when their modules must interleave.
-
-The object header carries owned class identities, initialization edges,
-completeness/static flags, and per-function may-touch summaries with diagnostic
-witnesses. Imports use those summaries even when their initializer bodies are
-not live in the importing compilation. Class layouts survive body elimination.
-Each `ClassCyclicError` witness holds existing owner/field name references, a
-numeric line/column, and a shared suffix for indirect calls; a null field denotes
-a call. Diagnostic text is formatted only when a cycle is rejected. Serialization
-stores shared witnesses once and preserves numeric locations without requiring
-the original source text.
-Ideal CLInitNodes also survive serialization, including source positions, so
-inlining an imported helper retains its active touches. Erasure now happens in
-Unlink, after serialization; executable checks still belong to checkpoint 3.
-
-Object lookup distinguishes class definitions from references to other objects.
-Objects serialize their own bodies; imported function pointers retain symbolic
-identities. Loading registers all classes supplied by each object, resolves
-prerequisites against existing source/imported units, and selects the requested
-nested class's type rather than always the object's root class. Cross-object
-calls are relinked after all bodies load, before Iter validates saved types.
-
-Validation covers separately compiled cyclic objects, a shared diamond across
-three worklist seeds, interleaving orders from two multi-class objects, imported
-helpers with and without inlining, type-only loading with stable flag aliases,
-and the emitted zero flag byte. The C0DE layout changed: rebuild existing objects.
-The runtime still does not test or set the flag, and static classification remains
-deferred. All 494 tests pass in the Chapter 25 Make suite. Logs:
-`build/init-checkpoint2-focused.log` and
-`build/init-checkpoint2-all.log`.
-
-### 3. CLInitNode and the executable protocol
-
-Files: new `node/CLInitNode.java`; `Node.java`, `Parser.java`, `CodeGen.java`,
-`Serialize.java`, `Eval2.java`, and the printer adapters as needed.
-
-- Add an effectful, serializable CLInitNode targeting the resolved CompUnit,
- with control/memory inputs and outputs in slots 0/1. Pending targets remain
- explicit until resolved. Its type computation is conservative about the
- initializer's whole memory effects; final normal output guarantees completion.
-- Integrate its possible call with existing Call/CallEnd linking, argument and
- return propagation, and body liveness. A reference to CompUnit metadata alone
- must not let SCCP discard the initializer as uncalled. Reuse the call machinery
- or a generated guard helper, rather than implementing a second independent
- calling convention in the node. Test an out-of-line imported initializer as
- well as an inlined one.
-- Insert checks at class-field accesses, including function-pointer loads;
- use the same operation for required instance creation and startup. Do not
- instrument ordinary function headers or rewrite their function-pointer data.
-- Insert `$init = true` once in each dynamic `<clinit>` body, naturally at entry.
- The checking node performs the test/conditional call and contains no store.
- A false startup test becomes a direct call preserving `arg` and the result.
-- Give Eval2 the same shared-object/flag behavior. Lower checks to existing
- control, memory, and call IR for machine compilation; use an out-of-line
- helper if necessary rather than adding target-specific machine operations.
-- Reject the presently unsupported native class/raw-initializer handoffs. Keep
- ordinary checked-field function pointers unchanged. Do not add an explicit
- initialization obligation to C drivers or other native callers. Direct native
- entry into a dynamic class remains outside the new guarantee until a
- compiler-managed startup/export mechanism exists.
-
-Checkpoint: sequential touches run the body once; parent effects precede child
-initialization; an untaken branch runs no child code. Verify result/argument
-preservation and flag sharing in Eval2, ARM/RISC emulation, and native execution
-through Simple startup. Existing direct native tests must not be mistaken for
-coverage of the deferred dynamic-export case.
-
-### 4. Parser flow and ideal simplification
-
-Files: `ScopeNode.java`, `Parser.java`, `CLInitNode.java`, and existing
-control/memory lookup helpers.
-
-- Add path-local known-initialized facts to ScopeNode. Copy in `dup`, intersect
- reachable predecessors in merges, and respect entry/backedge distinctions in
- loop completion, break, continue, and return handling. The established
- control/memory path must justify each check omitted by the parser.
-- Implement constant-true, constant-false, preceding dominating CLInit, and
- dominating true-test peepholes. Use dependencies to revisit proofs after SCCP
- or inlining sharpens values. Keep remaining parent requirements when only a
- child's own body is static.
-- Preserve initialization effects when folding field loads into function
- constants. Never hoist a check onto a path that previously did not touch the
- class. Do not confuse an early true flag inside `<clinit>` with permission
- to read that initializer's unfinished fields.
-- Add fully-static classification using the initializer body and its effects,
- excluding the generated flag store from the test. Emit a true initial flag
- only after proof; remove the now-redundant body/store/checks. Extend mutable
- constant-field lowering only where observations during initialization remain
- correct. Do not use classification to erase evidence of a genuine cycle.
-
-Checkpoint: distinguish a dominating check from a one-arm-only check, a
-zero-trip loop from a definitely executed touch, and a static child from an
-unfinished dynamic parent. Check that early flag folding cannot hide a cycle.
-
-### 5. Phase ordering and final composition
-
-Files: `CodeGen.java`, `ParseAll.java`, `Serialize.java`, `ElfReader.java`,
-`Main.java`, and the native linking path in `TestC.java` as appropriate.
-
-The current driver runs Parse, Iter, Opto, IterPostOpto, FinalizeIR, TypeCheck, LoopTree,
-`unlinkImports`, serialization, then selection and scheduling. Add explicit
-initialization-analysis boundaries without relying on a particular peephole
-worklist order:
-
-1. During parse/load, register identities and represent active initialization
- obligations in ideal IR. Ordinary Opto resolves targets and removes dead
- control; do not collect a historical union of temporary types or calls.
-2. After Opto finishes, collect surviving CLInitNodes and calls, complete
- function summaries, classify provably static work without circular
- assumptions, and validate the graph. Any remaining unknown required
- initialization effects prevent certification.
-3. Before `unlinkImports`, retain imported summaries needed for validation and
- serialization. Serialize ideal CLInitNodes and their proof-bearing control
- and memory edges, plus the independent requirements.
-4. Lower remaining CLInitNodes before final loop-tree construction and machine
- selection. Because lowering introduces branches/calls, rebuild affected
- control information and run ordinary ideal/type validation on that graph.
- Move the final LoopTree step if needed rather than using a stale tree from
- before expansion. Keep the serialized representation ideal and reloadable.
- Ensure the generated call participates in call linking before the final
- unlink step; imported bodies may remain represented by symbolic targets.
-5. Before native executable composition, load metadata for the exact full
- Simple object set being linked, merge requirements, and validate again.
- A plain GCC/ELF link is not an initialization-graph checker. Do the same
- validation for a complete in-memory image.
-
-If optimization or loading discovers another target, mark summaries dirty and
-rerun validation before emission. No runtime `initializing` state is introduced;
-compile-time DFS bookkeeping is independent of the single runtime flag.
-
-Checkpoint: source, imported ideal IR, and final linked metadata agree on
-acceptance/rejection for equivalent optimized graphs. Exercise different
-worklist seeds and object-discovery orders to check determinism; eliminated
-branches and targets must not leave stale dependencies. Missing required
-metadata fails diagnostically.
-
-### Validation and scope
-
-Use a small set of focused fixtures covering several boundaries each, plus the
-existing full Chapter 25 Make suites. Include effects before a nonreturning
-call so dependency checking cannot accidentally depend on returned memory.
-Exercise duplicate touches, conditional cycles, native-handoff diagnostics,
-serialization alias remapping, class/flag ownership, and normal startup results.
-Run the fixed allocation cohorts only after runtime semantics stabilize; graph
-and spill changes are expected, and any new golden counts must follow execution
-and register-correctness checks. Keep Jig tests ignored.
-
-Do not backport this feature, change pointer widths, introduce concurrent or
-recoverable initialization, add dynamic module loading, or solve the general
-native-export policy in these checkpoints. The extra analysis-only alias slice
-remains an alternative representation if explicit summaries prove awkward;
-implementing both would add work without improving the initial guarantee.
-
-## Module goals retained from the old notes
-
-Keep one-line programs easy to compile/run, filesystem-based nested namespaces,
-leading-underscore privacy, separate compilation without source include files,
-and ordinary object-file/native-linker interoperability. Classes may be declared
-within a file or supplied by nested files. Source and object builds must have
-the same initialization semantics. The current CLI is documented in the
-[chapter README](README.md); historical command examples and conflicting
-initializer-order proposals are superseded by this design.