summaryrefslogtreecommitdiff
path: root/skills/chapter25-codex-notes.md
diff options
context:
space:
mode:
Diffstat (limited to 'skills/chapter25-codex-notes.md')
-rw-r--r--skills/chapter25-codex-notes.md186
1 files changed, 141 insertions, 45 deletions
diff --git a/skills/chapter25-codex-notes.md b/skills/chapter25-codex-notes.md
index 878f231e..e0cd9c98 100644
--- a/skills/chapter25-codex-notes.md
+++ b/skills/chapter25-codex-notes.md
@@ -2,31 +2,23 @@
This file is intentionally AI-facing. It records durable invariants, debugging
habits, and project-owner preferences that are easy to miss when reading only
-the implementation. Use the current checkpoint below rather than old session
-handoffs or historical branch/status reports.
-For cross-chapter work, read [the backport queue and validation record](../docs/chapter-backports.md).
-It owns pending reproductions and a condensed completion record; these notes
-capture reusable lessons. Detailed superseded validation history remains in Git.
+the implementation. Check current code and Git state rather than treating old
+session handoffs as live status.
+For cross-chapter work, read [the backport queue](../docs/chapter-backports.md).
+It owns pending work; these notes capture reusable lessons. Completed work and
+superseded validation history remain in Git.
+The [approved chapter split](../docs/chapter-split.md) owns the teaching outline
+for current Chapters 24-25. Cliff approved it on 2026-10-19: two SCCP chapters,
+one combined incomplete-type chapter, then construction, escape analysis,
+modules/class initialization, and separate compilation. Library construction
+and optimizer tuning are appendices. Use descriptive chapter names while the
+count changes. Cliff approved starting with both proposed boundaries and wants
+to review/play with construction versus escape after the snapshots run. Keep
+that border open to refinement. Revisit 17a/17b when choosing final names or
+numbers. The first extracted snapshots are chapter-sccp and
+chapter-interprocedural-sccp; numbered 24/25 remain comparison baselines.
For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.md).
-## Reset checkpoint (2026-10-03)
-
-- Cliff committed the chapter reorganization and mutability split as `22df8918`
- (`Chapter renumber, and split ch 17`). Implementation and validation are done;
- there is no unfinished compiler port or hung validation process to resume.
- Check current Git state before treating this checkpoint as live status.
-- Cliff subsequently committed the Chapter 18 README review and backup-file
- deletion as `3c13b271` (`Update README.md`). The tree was clean before this
- memory refresh; only these two notes files are left modified by the refresh.
- Do not commit or push without a new instruction.
-- Read the mutability section and validation record in
- [chapter-backports.md](../docs/chapter-backports.md) for scope and results.
- Full affected chapter suites, Chapter 25 native tests, standalone 17a/17b
- builds, and exporter checks passed. Logs are disposable under
- `build/mutability/`; no need to repeat suites without a new change or concern.
-- Next work is Cliff's review, not another inferred cleanup task. The separate
- pending queue still includes float-to-int syntax and deferred BOTTOM contracts.
-
## Collaboration preferences
- For densely keyed integer maps, use indexed `Ary<T>` storage rather than
@@ -74,14 +66,78 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m
## Mutability and chapter boundaries
-- Chapter 25 phase split (2026-10-17): Opto now only runs SCCP, numeric-mode
+- Native function addresses in AMD64 ELF use low-address tail-jump thunks.
+ Static fields and computed addresses must name the same weak entry across
+ objects; direct calls can still use the native PLT. A native FunPtr has no
+ local FunNode, so Encoding must check externFunc before the dead-function
+ identity fallback. Do not suppress relocation overflow checks or make class
+ storage read-only to accept a narrow dynamic relocation. Preserve constant
+ initialization Store folding. See StaticDataTest.testNativeFunctionIdentity.
+- Long-term pointer direction: compact code and data arenas, which may be
+ separate; full-width native pointers have separate types and lifetime rules.
+ Keep static class images viable for ROM and eventual removal of initialization
+ checks. These are future changes, recorded in the Chapter 25 roadmap; the
+ native thunk fix keeps current four-byte function/eight-byte data pointers.
+- The class-init-cycle project is closed. Cycle checking, diagnostics,
+ serialization/import handling, and runtime lowering are complete for the
+ agreed scope. Argument arrays and initialization optimizations remain
+ separate ROADMAP work; they do not keep this project open.
+- Native boundary decision (2026-10-18): no classes for native code; do not
+ design a native-startup policy. Ordinary supported FFI calls are separate.
+ Chapter 25's README owns the tutorial explanation of class initialization;
+ ROADMAP owns future work. Do not recreate an initialization progress document.
+- GCM memory-Phi anti-dependencies constrain paths whose input differs from
+ the load's memory, not paths carrying that memory unchanged. Bound the idom
+ walk by the memory definition's depth: an alternate Phi input need not be
+ dominated by that definition. Also stop at `defblk.idom()`: when the memory
+ definition is the function entry, its null idom is the valid sentinel.
+ Include the boundary depth; a strict `>` skips required same-block edges
+ and can make a read observe a later store. Keep `testCapitalize`,
+ `testConditionalClassInitX86`, and
+ `testReadBeforeConditionalStore` passing; dropping Phi handling entirely
+ miscompiles the latter (140 instead of 120).
+ This rule applies from Chapter 13 onward. The nested-branch regression
+ `SchedulerTest.testMemoryPhiAcrossSiblingBranch` checks the disjoint walk;
+ removing the depth bound reproduces a null-stblk crash. Retain each early
+ chapter's evaluator scheduling rules when porting the boundary.
+- CLInit lowering (2026-10-17): TypeCheck queues surviving markers; optional
+ Serialize saves opaque ideal IR, then Lower runs Iter before LoopTree.
+ Serialization includes disconnected Returns and filters cross-object links
+ without mutating the live graph. Parent initialization precedes the child's
+ flag test/call; the true flag store remains inside the initializer. Runtime
+ flag reads follow ordinary Load idealization, including removing control.
+ Handle x86 condition-flag lifetime constraints in the backend, not by pinning
+ ideal Loads. Generated calls remain unlinked and
+ conservative; do not reopen parameter flow into SCCP-optimized bodies.
+ Private/imported initializer bodies retain Start because opaque CLInit has no
+ ordinary Call edge yet; this does not change public reachability.
+ The selected entry gets the external arg; other clinits get zero for now.
+ The planned array ABI uses non-null entry args and null elsewhere; not yet
+ implemented. Eval2 shares singleton class objects and retains imported bodies.
+ Execution coverage includes native cold/hot calls, parent order,
+ ARM/RISC-V encoding, and nonreturning object reload.
+- Chapter 25 phase structure: Opto only runs SCCP, numeric-mode
resolution and worklist seeding. IterPostOpto does peepholes/inlining;
FinalizeIR does field layout/offset cleanup and function/CU finalization.
opto() requires an explicit prior iter(). Direct calls needing the old complete
optimization sequence also name iterPostOpto().finalizeIR(); driver() handles
- the sequence for later targets. The full 505-test Make suite passes. Earlier
- chapter backports are pending in docs/chapter-backports.md. CLInit expansion
- and serialization ordering are not part of this refactor.
+ the sequence for later targets. Iter setup, FinalizeIR, TypeCheck, Unlink,
+ and InstSelect own their algorithms in separate files. CodeGen's phase wrapper
+ handles assertions, timing (including failures), and successful viewer phase
+ notifications. Its driver lists the order and skips completed steps.
+ Serialize decides whether ideal IR is needed: explicit Serialize or file
+ Export, not Encoding or in-memory linking. A staged file export must request
+ Serialize before Lower; ideal IR cannot be recovered from machine nodes.
+ Unlink detaches imports for machine selection, including requests stopping at
+ Select, while ideal evaluation retains them. Schedule requires Unlink or Select;
+ use the driver instead of skipping Lower/LoopTree in hand-written test chains.
+ Earlier chapters expose only phases with real work. CodeGen and its resumable
+ driver start in Chapter 18, with explicit LoopTree and LocalSched boundaries.
+ Instruction selection arrives in 19, RegAlloc in 20, Encoding/Export in 21.
+ FinalizeIR starts in 22 with unused-function cleanup and gains layout in 23.
+ Opto (SCCP) and IterPostOpto start in 24; earlier peepholes are named Iter.
+ Serialize, Lower, and Unlink stay in 25. Use short explicit chains such as
+ parse().iter(), and driver(Phase.target, ...) for longer compilation paths.
- Class-init memory checkpoint (2026-10-17) is implemented: keep CLInit
opaque through Iter/Opto, carry whole-memory effects plus the known-true
`$init` alias result, and use normal parser memory for redundant checks.
@@ -89,12 +145,12 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m
up front; ordinary Load/FldOff nodes resolve forward receivers. The fourth
CLInit input is always present in a constructed graph. If the receiver resolves
to an instance, CLInit and its speculative flag load disappear together.
- Precise alias recovery can still be deferred. See `chapter25/initialization.md`.
- Runtime lowering and startup adaptation remain deferred. Public function fields
+ Precise alias recovery can still be deferred. See `chapter25/README.md`.
+ Runtime lowering is implemented above; argument arrays remain deferred. Public function fields
on instances of public classes must retain Start, just like class-object
fields. publicFIDX now checks both; this fixes sys.scan.match losing Start
- while building sys.o and subsequently widening after import. All Chapter 25
- Make suites pass (505 tests). Do not weaken assertions or reset imported types
+ while building sys.o and subsequently widening after import.
+ Do not weaken assertions or reset imported types
to conceal incorrect public classification.
Future optimization: move final, always-constant instance fields to the class
or fold them to constants and omit their instance storage. Access semantics
@@ -157,11 +213,22 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m
deliberately retains immutable shallow struct references resolved by name;
see its README's L0/L1 notes before trying to expand recursive types.
Keep Chapter 25's separate constructor-memory and incomplete-type design.
- See the concrete boundary notes
- in `docs/chapter-backports.md`. GCM readiness and anti-dependency checks must both
+ GCM readiness and anti-dependency checks must both
filter by alias and ignore MemMerge as a clobber; keep the evaluator's same
alias filtering too. MemMerge still needs ordinary data-dependency placement.
+- From Chapter 15, an array element Load's control stands for its range check.
+ Keep that edge during parsing and rewriting. A Load can push through a
+ memory Phi when its control is null or dominates the Region's idom; retain
+ that control on both copies. The Region itself or a later check cannot
+ supply control to predecessor loads. Dropping it permits an out-of-bounds
+ speculative read. The parser currently attaches the current control as a
+ placeholder, which can be the Region itself even in a valid graph.
+ `Chapter15Test.testArrayLoadControl` checks a guarded read after a memory
+ merge; `testArrayLoadDominatingControl` checks successful splitting under
+ an earlier common check. Length loads have no index;
+ Chapters 19+ already leave them unpinned, independently of element control.
+
- Chapter 15 New consumes `{ctrl, $mem, size}` and produces `{ptr, $mem}`:
no control result, pointer slot 0, memory slot 1. Its TypeStruct/TypeMemPtr
defines the covered field aliases. Use a partial input MemMerge (null default),
@@ -276,25 +343,35 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m
User-count dependencies must wake from Node.delUse, including unkeep.
- Generalized Phi factoring now starts in 10 and is forwarded through 25.
- Memory-specific eligibility lives in `MemOpNode.canDrop`, with virtual Load
- and Store checks. Load owns `clobbered`: all these chapters check only
- immediate memory users, without recursion or a visited set. Stop at Stores,
- Phis, MemMerges (11+), New (15+), and Calls (18+); do not chase aliases or aggregates just
- to save one Load. Keep this teaching optimization simple and conservative.
+ Memory-specific eligibility lives in `MemOpNode.canDrop`. Store identity
+ equality now excludes Stores from this rewrite. Load owns `clobbered`: all
+ these chapters check only immediate memory users, without recursion or a
+ visited set. Stop at Stores, Phis, MemMerges (11+), New (15+), and Calls (18+);
+ do not chase aliases or aggregates just to save one Load. Keep this teaching
+ optimization simple and conservative.
Preserve exact operation attributes with clone-based `copyEmpty`, type each
operand Phi separately, reuse identical inputs, and reject type widening.
- Stores require sole use by the Phi; Loads inspect memory users for clobbers
- before GCM has made anti-dependence edges. From 11, create precise memory
+ Loads inspect memory users for clobbers before GCM has made anti-dependence
+ edges. From 11, create precise memory
Phis for the factored operands. Do not factor aggregates or bulk Phis;
wait for other BulkMemPhis at the Region to disappear, since their slice
- lookup assumes Region/alias identifies the completed memory point. Existing
- control-bound Stores in 13-14 remain ineligible. `delUse` wakes recorded
- dependents for user-count queries; Region's empty-diamond fold depends on
+ lookup assumes Region/alias identifies the completed memory point. `delUse`
+ wakes recorded dependents for user-count queries; Region's empty-diamond fold depends on
projection rewiring as well as projection types. These guards are already
forwarded through 25; preserve the later chapters' existing `copyEmpty`.
`subsume` must wake each rewired user's recorded dependents, just as `setDef`
does: operand identity changes can enable a Phi fold without changing the
intervening operation's type (`Chapter14Test.testCloneAnd` in 16).
+- Stores use identity equality from Chapter 10 onward. Equal Stores on two of
+ three paths must not merge and run on the third path. Sharing requires the
+ original Stores to cover every exit path, or GCM to split the shared Store
+ back onto those paths. GVN checks neither, and GCM does not split Stores.
+ This also disables generic Phi factoring of Stores, but keeps explicit
+ store-after-store elimination and the closed-loop sink introduced in 17b.
+ `Chapter10Test.testConditionalStoreSharing` returns the object to expose the
+ actual write; returning a Load could hide it through forwarding.
+ `Chapter17bTest.testSinkLoopStores` checks exit placement and zero-trip values
+ in 17b and every later snapshot. Do not restore Store GVN to recover a fold.
- Find the chapter where the relevant feature first appears, not just the chapter
that can parse the original reproducer. Reduce away later syntax/features when
possible. Put the regression in that earliest `ChapterNTest.java`, and forward
@@ -305,8 +382,8 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m
affected directories explicitly listed. Establish the destination baseline
before changing it; older Makefiles may need a forced rebuild after API changes.
- Keep unrelated discoveries separate in `docs/chapter-backports.md`. Completed
- fixes leave the pending queue; retain a concise validation summary and move
- unresolved discoveries out of the history into the actual queue. A green suite
+ fixes leave the pending queue; record validation in the change description.
+ Keep unresolved discoveries in the actual queue. A green suite
after changing graph/test order does not prove an intermittent failure fixed.
- Store disposable probes/logs under an appropriate ignored build directory.
Preserve durable reproducers in tests or the review record, not only scratch
@@ -512,6 +589,18 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m
before coloring, a fixed-register definition can clobber despite `_reg==-1`.
Cloning is valid only if its output mask can satisfy the use. A fixed-register
clone with flexible uses needs use-side splits, not a no-op def-side path.
+- Chapters 20-25 flag spills use specialized splits that exclude FP registers.
+ x86 can transfer FLAGS directly to a GPR or stack slot. ARM's
+ `SplitFlagsARM` transfers NZCV through a GPR and overrides
+ `isNonStackSplit()`; RegAlloc includes these transfers in loop-depth
+ collection and subsequent splitting. `SplitGPRARM` then admits GPRs and
+ stack slots, preserving the FP exclusion through cascading spills.
+ Keep ordinary splits excluded from those allocator paths. Test both flags
+ clobbers and simultaneous flags/GPR clobbers: `testFlagSpills` executes the
+ allocated moves for all 64 x86 status-bit and 16 ARM NZCV combinations from
+ Chapter 21 onward. Chapter 20 checks allocation and masks only; encoding
+ begins in 21. Preserve each allocator's existing ordinary-split policy:
+ 20-22 always skip ordinary splits here, while 23-25 skip them when min!=max.
- Check function ownership and register masks immediately after allocation,
before encoding rewrites tail calls or adds untyped branches. Call.regmap can
itself depend on CFG.fun/idom. The test-only CheckedCodeGen override keeps
@@ -690,6 +779,13 @@ Consequences:
widen only below-center nonconstant ranges: above-center widening can fall to
an unwidened constant, violating SCCP monotonicity. Existing Chapter14/21 tests
exposed this during the backport.
+- Recursion needs two widening points in Chapters 24-25: ParmNode for arguments,
+ and CallEndNode for results. The return cycle in `f(x-1)+1` never crosses the
+ Parm. Keep the comments at both sites and PhiNode cross-linked. Chapter 25's
+ FunPtrNode follows the live Return range; join that range before widening,
+ then bound widening by the callee signatures, not that live range. Apply the
+ same rule before and after linking, and retain narrow results such as i32.
+ `Chapter24Test.testRecursiveWidening` checks both cycles in both chapters.
- TypeConAry's `_make(any,widen)` shares the backing array and returns an
uninterned type. `xdual()` must leave interning to Type.intern's dual-pair setup;
ordinary factories and ymeet's widening replacement intern their results.