summaryrefslogtreecommitdiff
path: root/docs/chapter-backports.md
blob: 7269babc9752943004a452ebb1b57bdb7137f3d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
# Chapter backport review

This is a queue of pending work. Completed corrections leave the queue; the
[validation summary](#validation-record) records their scope. Detailed old logs
are disposable build artifacts. Reusable rules live in
[the AI notes](../skills/chapter25-codex-notes.md).

Introduce independent fixes in the earliest applicable chapter and propagate
them through every affected snapshot. Testing Chapter 25's inherited tests does
not validate the compilers in the earlier chapter directories. Chapter 10 is
now split into 10a (bulk memory) and 10b (lazy memory partitioning). Keep the
rest of SSA construction with incomplete types in Chapter 25 for now. Cliff wants to revisit splitting Chapter 25
into smaller chapters; escape analysis remains in 25 until that larger review.

Reset checkpoint (2026-09-28): Cliff committed the completed FunPtr work as
`75390f47` (`Backport FunPtr`), following `586c8f65` (direct C data bindings) and
`8212a66f` (constructor checks). The working tree was clean before this notes
update. No FunPtr implementation or validation remains outstanding; the full
21-25 results are recorded below. The subsequent narrow C integer-return ABI
correction starts in 22 and is forwarded through 25; see the validation record.
Splitting Chapter 25 is deferred, not an instruction to renumber.

## Pending corrections

- **Forward Chapter 10b memory partitioning through 22-24.** Cliff reviewed
  and committed the port through 20 in `f01ee052`. Chapter 21 is implemented
  and tested; stop here for review of encoding and execution. Parameters, calls, and returns carry whole memory; aliases split
  lazily within functions.
  Keep private constructor memory, escape tracking, and incomplete-type
  inference in 25. Carry generalized Phi factoring, its one-step Load guard,
  and dependency fixes with the next ports; later snapshots already have parts
  of that rewrite. Ideal Calls must keep their last input as the function pointer; direct machine
  calls embed that target. Scheduling must not append anti-dependence edges to
  either argument list.

### Chapter 15 allocation design (implemented)

Chapters 12-14 retain explicit initialization Stores. Chapter 15 replaces them
with New's zeroing operation while keeping one parser-visible `$mem`.

| Interface | Chapter 15 |
|---|---|
| New inputs | `{ctrl, $mem, size}` |
| New outputs | `{ptr, $mem}`; no control result or per-alias projections |
| Coverage | field aliases from New's internal TypeStruct/TypeMemPtr |
| Input memory | partial MemMerge, no default, entries for affected aliases |
| Result integration | whole-memory MemMerge, prior default, affected entries all using the same New memory projection |
| Memory types | existing alias plus stored-value type; no lattice extension |

A null MemMerge default means absent aliases are uncovered. Empty structs
therefore have an empty partial input; their allocation does not change the
whole-memory aggregate. New's output also covers only its own aliases.

An alias-specific contents query combines incoming values with the field's
initializer from New's struct (integer zero, floating zero, or null). It records
optimizer dependencies and stops at cached Phi types to break loops. MemPhi
merges the scalar contents of its precise alias. Start's incoming heap is empty
in this chapter; functions with heap arguments will require a different entry
query in Chapter 18.

Loads fold against their own New and bypass a proven distinct allocation via
New's memory input. Different pointer nodes alone do not establish disjointness.
Array length remains a subsequent Store; array access control inputs remain.
Global code motion and the evaluator scheduler both follow an alias through
MemMerge to discover the allocation's ordering constraint. MemMerge is packaging,
not itself a clobber. The viewer and evaluator use the new slot conventions.

### Chapter 16 constructor integration (reviewed)

ScopeNode now owns the variable records and one `$mem` binding; the obsolete
ScopeMin alias table is removed. New inputs are `{ctrl, $mem, size, fields...}`,
with initializer inputs in struct-field order. Outputs remain `{ptr, $mem}`;
partial memory coverage and whole-memory integration are unchanged from 15.
Alias contents read initializer types with dependencies, rather than assuming
zero. The evaluator reads these same inputs, including array length. Constructor
body effects participate in ordinary lazy branch/loop memory SSA.

The sole-use Store-to-New check is deliberately unchanged. A shared allocation
memory result has more users than a per-alias result, so it permits fewer folds.
`Chapter15Test.testBasic5` retains a `.y` Load instead of folding it to 3.14;
both runtime results remain checked. Other changed printed expectations only
adjust Region/Loop IDs after removing parser memory nodes.

### Chapter 18 function memory (reviewed)

Chapter 17 retains readonly casts, forward reference type updates, lexical
guards, and increment/assignment semantics while replacing ScopeMin with one
memory binding. Chapter 18 keeps its top-level Var class and makes MemMerge an
ordinary optimizer node rather than a parser alias table. Memory Parm 1 stays
opaque; local branches, loops, and merged returns use BulkMemPhi/MemPhi.
Function entry and CallEnd memory have unknown contents, including heap arguments.
Calls consume and return the complete memory state. There are no alias summaries
or private constructor effects. Inlining can expose the existing alias rewrites.

New retains `{ctrl, $mem, size, fields...}` inputs and `{ptr, $mem}` outputs.
The evaluator, Eval2, graph viewer, and scheduling use those slots. Call is a
clobber for every alias. GCM raises a Load before the Call's block terminator;
it must not append the Load to the Call's inputs, which encode arguments and a
last-slot function pointer. The one-step Phi Load guard also stops at Calls.

### Chapter 19 instruction selection (reviewed)

Both selectors preserve BulkMemPhi exclusions and MemPhi aliases. Selected
memory instructions carry their alias through MemOpNode; GCM follows aggregates
for ordinary and folded reads, waits for matching writers, and inserts ordering
edges that require no register. Direct machine calls embed their target and must
keep their argument lists intact, just like ideal Calls.

Preserve this chapter's existing lowering of nonzero initializers into Stores.
New now takes `{ctrl, $mem, size}` and produces `{ptr, $mem}`; there are no field
value inputs at this stage. Partial coverage and whole-memory integration are
unchanged. New's alias contents meet incoming memory with the field's zero type.
Both CPU allocation register masks and both evaluators use the new slots.

The nested-loop regression reaches x86's existing right-hand Load/Add TODO;
that commutative case now uses AddMemX86 just like a left-hand Load. No other
unfinished instruction patterns or encoding work is included.

### Chapter 20 register allocation (reviewed)

ARM joins x86-64 and RISC-V in preserving bulk/precise memory Phis, allocation
slots, and selected memory effects. The allocator already excludes TypeMem Phis
and gives memory/ordering edges no register constraints; no allocator heuristic
changes are needed. Allocation regressions check that memory stays unallocated
and the pointer projection receives a legal register on all three targets.

Folded x86 Add must retain a value input for its two-address result even when
that value is constant. The old immediate form omitted input 4 and caused a null
live range in BuildLRG. Both left- and right-hand Load patterns now retain the
other addend; the existing constant selector materializes it.

Keep Chapter 20's inlining-safe Return typing and the dependency registrations
on a rejected Phi factoring attempt. Those fixes predate this memory port.

### Chapter 21 encoding (implemented, review checkpoint)

All three targets retain the memory Phi subclasses and New's pointer slot 0,
memory slot 1, and size input 2. Chapter 21 caches New's register masks in the
shared node during register allocation; update that shared interface. Keep its
existing scheduler rule that writers are already placed when adding read-before-
write constraints. Calls receive no scheduling-only operands.

Dependency registration reverses direction here: `consumer.addDep(producer)`.
The forwarded memory queries and explicit forward dependencies follow that API.
Synthetic no-exit loops collect MemPhi inputs by alias and use BulkMemPhi for
the merged return memory. The one-step Load factoring guard is unchanged.

Actual execution exposed a full-width integer Load allocated to XMM but encoded
as a GPR load. LoadX86 now selects the encoding from the allocated register bank,
preserving register masks. ARM's emulator now supports MOVK and MOVZ's complete
16-bit immediate and halfword shift. Carry these narrow fixes with later ports.

BrainFuck follow-up: both the original and ported compilers retain 12 ideal
Loads and 114 Stores (106 Stores initialize the program). In the RISC-V Hello
World run, both execute 3,261 heap loads and 646 heap stores; stack loads/stores
change from 15/15 to 1,006/572, mostly spilling `d+4`. There are also pre-existing
missed length optimizations: `program#` reloads the known 106 on all 907 loop
tests, and `old#` is reloaded after allocating the replacement output array.
Load forwarding stops at memory Phis; its loop-profit test does not see through
the backedge's multiway Phi. The old-output pointer is itself a Phi, beyond the
two-direct-allocations disjointness test. Field aliases distinguish array length
from array data, but do not distinguish separate arrays of the same element type.
A scratch source replacing only `program#` with 106 passes Hello World, removes
907 heap loads, and returns stack traffic to 15/15. Its weighted split score
nevertheless rises from 130 to 165 as allocation inserts many pointer copies
before initialization stores; this score is not a dynamic memory-traffic measurement. No compiler
optimization was changed in this investigation. Probe sources and before/after
logs: `build/BrainMemory{Probe,Variants}.java`, `build/brain-memory-*.log`, and
`build/brain-variants-*.log`.

The subsequent Chapter 21 Load search now folds `program#` through the loop.
The search returns a folding witness without creating nodes or rewiring edges;
it retains optimizer dependencies. At a non-loop MemPhi it must prove every
arm, with a BitSet remembering successful merges. Returning to the original
loop memory is success only for an invariant pointer. Unknown clobbers, other
loops, and incomplete merges fail. A separate builder creates value Phis after
the proof; unchanged paths refer to the new loop value Phi. Enqueue that Phi:
a self-edge can otherwise keep an obsolete Phi alive after local folding.
Controlled array accesses are not hoisted across a merge. The one-step
`Load.clobbered` factoring guard is unchanged. Carry this search with later ports.

The unmodified BrainFuck source now has 11 Loads. RISC-V Hello World executes
2,354 heap loads, 646 heap stores, and 15/15 stack loads/stores, compared with
3,261 / 646 / 1,006 / 572 before this search. Emulator instruction count drops
22,965 to 21,920 (not a hardware timing). Retained/weighted moves change
46/130 to 144/165 as allocation inserts repeated initialization-store pointer copies.
Four reduced cases cover unchanged N-way arms, a possibly aliasing arm,
loop-carried pointers, and distinct values folding on all arms; they pass
2,800 evaluations across 100 seeds. The existing 9,800 memory evaluations pass.
At that checkpoint, the 432-test suite had only the user-edited BrainFuck golden failure
(42 versus 165). A temporary test copy restoring native BrainFuck execution
and using 165 passes all 91 spill-cohort entries and their runtime checks:
943 / 1,594 moves / weighted moves. The user's test edits were preserved.
Logs: `build/load-search21-tests5.log`, `build/load-search21-seeds.log`,
`build/load-search21-loop-seeds.log`, `build/brain-variants-search.log`, and
`build/load-search21-spills-complete.log`.

### Adjacent-copy forwarding: Chapters 21-25

Chapter 21 now removes a post-color Split when its sole use is the immediately
following non-Phi, non-CFG instruction, the operand accepts the source register,
and the operand is not two-address tied. Adjacency proves no intervening clobber.
The same rule and focused guard regression are present in 22-25; Chapter 20
retains its baseline allocator. No spill-selection or CFG splitting rule changed.

RISC-V BrainFuck keeps its original source and now has 35 / 42 retained / weighted
moves instead of 144 / 165. Hello World executes 21,787 emulator instructions
instead of 21,920; heap traffic remains 2,354 loads / 646 stores and stack traffic
15 / 15. Full Make suites and fuzzer targets pass in 21-25. Chapters 21-24 also
pass complete spill reports after refreshing reduced-move expectations:

| Compiler | Allocations | Retained moves | Weighted moves |
|---|---:|---:|---:|
| 21 | 91 | 821 | 1,444 |
| 22 | 117 | 826 | 1,470 |
| 23 | 147 | 913 | 1,711 |
| 24 | 214 | 1,310 | 2,969 |

Cohort 22 includes the two zero-move C return ABI checks added since the older
24-entry audit. Chapter 25's frozen 212-entry manifest remains unchanged.
Its reporter currently fails on ten pre-existing String-constructor parse errors;
the unmodified allocator reproduces all ten. The same 216 successful allocations
(including 14 current cohort-25 allocations) improve from 2,563 / 5,496 to
2,494 / 5,399, but these are partial totals. Fresh system-library encoding and
native cohort-25 checks pass. Logs: `build/copy-forward21-spills-final.log`
through `build/copy-forward24-spills-final.log`, `build/copy-forward25-spills.log`,
`build/copy-forward25-baseline.log`, and `build/copy-forward*-tests*.log`.

### Other pending corrections

- **Chapter 25 historical String fixtures.** Ten replay entries fail before RA
  because `Test.String.cs` is not fully initialized. They come from
  `Chapter21Test-testStringExport.smp` (four entries),
  `Chapter21AllocTest-testString.smp` (three), and
  `Chapter23AllocTest-testString.smp` (three), under
  `chapter25/src/test/java/com/seaofnodes/simple/spill/`. Both original and
  adjacent-copy-forwarding allocators reproduce them. Adapt these constructor
  fixtures separately and document the comparability change; do not omit the
  failed entries from a claimed complete spill total. The ordinary Chapter 25
  suite passes, including its current String programs.


- **Chapter 21 read-before-conditional-store scheduling.** A reduced negative
  test for Load search exposed an existing ordering error: summing `a.x` before
  a possibly aliasing conditional store in a loop returns 140 instead of 120
  when the pointer aliases `a`. It reproduces with the saved pre-search Load
  class as well. Source and before/after graph dumps are in
  `build/LoadSearchProbe.java` and `build/load-search-probe-{before,after}.txt`.
  The Load-search rejection tests put the Store before the read to isolate
  the optimization from that separate scheduling defect. Investigate separately.

- **Forward Chapter 21 whole-program printer ordering through 22-25.**
  `CodeGen.toString()` before scheduling now uses function-local CFG RPO and
  groups every Region/Loop with its Phis. Chapter 25 supplied the function
  ownership and grouping approach, but its combined CFG/data walk still puts
  BrainFuck's closing Region before the body when adapted to 21. Chapter 21
  therefore orders control edges first, then places data for display using
  private identity maps. No scheduling, idom queries, or graph mutation occurs.
  The depth-limited node printer and scheduled whole-program printer retain
  their existing entry points. Checked 18 parse/opto/loop-tree snapshots for
  complete unique output, CFG ordering, Phi adjacency, repeatability, and
  unchanged graph/cache state. Updated BrainFuck dump:
  `build/brain-print-after-opto.txt`; diagnostic probe: `build/Print21Check.java`.
  Release passes. The current full suite passes 430/431 tests; the sole failure
  is the debugger-edited BrainFuck spill golden (42 versus the unchanged 130).
  Those test edits were preserved. Log: `build/printer21-tests.log`.

- **Chapter 21 synthetic TOP return allocation.** The original compiler at
  `f01ee052` and the memory port both fail in `Coalesce.coalesce`: a Split of
  TOP has no input live range. Reproduction: `struct S { int x; int y; };
  S !a=new S; if(arg) while(1) { a.x+=arg; a.y+=a.x; } return a.x+a.y;`
  through Encoding on x86/SystemV. The new memory regression checks both
  precise slices of the synthetic exit, then schedules all three targets;
  allocation of its scalar TOP arm remains separate work. Do not hide the
  missing live range with a null guard in coalescing.

- **Existing Chapter 18 floating-array assertion.** The unchanged
  `TypeStruct.makeAry` assertion accepts integers and nullable references but
  excludes TypeFloat; `return new flt[1];` fails under `-ea`. The Chapter 18
  memory probe uses floating struct fields for initialization coverage instead.
  Investigate separately; this port does not change the array type lattice.

- **Chapter 24 Load BOTTOM-on-error backport, unwound/deferred (2026-09-28).**
  Cliff requested starting with 24 and analyzing failures before proceeding.
  Literal global BOTTOM memory/pointer inputs already propagate BOTTOM. The
  missing behavior is `LoadNode.compute()` returning BOTTOM instead of the
  declared field type when `err()!=null`; Cliff unwound that one-line experiment.
  Chapter 25's Convert nodes supply the surrounding expression/loop's typed
  result while retaining the erroneous BOTTOM-producing Load for TypeCheck.
  Chapter 24 relies on the Load itself supplying that typed result. Defer this
  backport with the conversion split rather than importing it alone.
  The experimental full Make suite had four failures out of 477
  tests (`build/load-bottom-24.log`); the fuzzer recipe was not reached.
  - `BrainFuckTest.testBrainFuck`: during parsing, the unfinished loop Phi for
    mutable `program` has its declared nullable type. The element load becomes
    BOTTOM, and reading the initialized `command` local triggers Parser's
    BOTTOM-means-uninitialized check before the loop can finish. Reduced valid
    source: `var !program="a"; for(int pc=0; pc<program#; pc++) { var command=program[pc]; return command; } return 0;`.
  - `Chapter10Test.testWhileWithNullInside`: the loop's result Phi starts at its
    declared `i64` while incomplete. Closing the loop exposes a BOTTOM Load on
    the backedge; Phi.compute explicitly propagates BOTTOM, violating the
    pessimistic `setType` narrowing assertion. Trace: Phi#1458 goes from i64 to
    BOTTOM with Load#1462 on its backedge. This occurs during parsing, before SCCP.
  - `Chapter10Test.testNullGuardErrors`: ReturnNode sees BOTTOM without any
    recorded scalar return kind, so its `No defined return type` error wins
    before the Load's `Might be null` diagnostic.
  - `Chapter13Test.testLinkedList0`: `head.next` becomes BOTTOM, and parsing the
    following `.i` reports `Expected reference but found Bot` first.
  Scratch instrumentation lives entirely under `build/load-bottom-probe`.
  Reduced sources pass or report the intended null error with the HEAD Load
  compiled separately, and reproduce the failures with the one-line change
  (`build/load-bottom-reduced-baseline.log`, `build/load-bottom-reduced.log`,
  `build/load-bottom-trace.log`). No earlier chapter was changed. Fixing these
  requires coordinating parse-time incomplete types, typed loop-Phi bounds,
  and diagnostics; the assertion and expected errors have not been weakened.

- **Chapter 25 BOTTOM-on-error backport boundary (2026-09-28).** Cliff requested
  investigating the earlier arithmetic contracts. In 24, changing ArithNode's
  non-integer fallback to global BOTTOM breaks the valid `testCoRecur`: the
  declaration `val az = x*2` refers to `x` defined later, and the allocation-time
  check confuses its temporary BOTTOM with an unset field. Three arithmetic
  diagnostics also become generic parser errors (`testNullRef5`, `testMaskFloat`,
  `testSubZeroTypeError`). Full-suite evidence: `build/arithmetic-contract-24.log`.
  Restoring the integer fallback fixes all four in a 40-test focused run
  (`build/arithmetic-top-24.log`). The Java return signature itself has no typed
  caller dependency. Further BOTTOM work is paused for Cliff's decision about
  separating forward/unresolved expressions from initialization errors in the
  earlier parser. The independent Load experiment is recorded above. The
  known-null regression is already rejected
  during parsing in 10-24. Keep this separate from the constructor/memory fixes.

## AOT class initialization: larger independent work

Proposed on 2026-09-19; deferred behind the smaller queue above. This is
a substantial, self-contained Chapter 25 change, not an entangled backport.
Earlier chapter applicability has not been established.

The goal is to pre-allocate AOT class objects in ELF data and pre-fill their
fields, letting ordinary graph optimization remove redundant initialization.
Lazy-loaded classes retain runtime initialization for now.

- Represent static object creation with a dedicated node, or a ConstantNode
  carrying a TypeStruct with the appropriate final field values. Preserve the
  class's identity and canonical layout independently of its current field
  values; equal contents must not merge distinct class objects.
- Use normal StoreNode peepholes to remove same-value-over-same-value stores.
  If needed, add a peephole that folds initializing stores into the static
  creator's type. Create a replacement ConstantNode/type; never mutate a shared
  constant's type. Prove that folding preserves initialization ordering and
  does not expose a final value prematurely through another reference.
- When `<clinit>` reduces to returning the static object, with no remaining
  I/O or public-memory effects, its runtime work can disappear. Initially it
  is sufficient to emit a trivial `<clinit>` and keep its calls. Omitting the
  function or calls is a later optimization; mixed initializers retain their
  remaining effects. Adapt this conceptual return to the existing class-memory
  representation rather than assuming the current `<clinit>` returns a pointer.
- Extend encoding's relocation information to describe addresses stored inside
  data: functions, other class objects, and constant objects such as strings.
  Emit the required data-section relocations, including their symbol, field
  offset, width, and addend. Keep native linking and emulator linking consistent.
- Give the function and data distinct stable symbols, e.g. `A.B.<clinit>` and
  `A.B.$class`. The defining CompUnit owns the class object and emits its one
  definition in its ELF. Other compilation units, including source currently
  being compiled, reference those symbols without emitting another copy of the
  class data. Unifying loaded Simple types/IR alone does not unify native data;
  preserve ownership and identity across partial compiles and serialization.
- Target four-byte function **and data** pointers, with code and data addresses
  restricted to the low 4 GB. Function pointers stay four bytes; data pointers
  must be brought into agreement. Account for layout, loads/stores, relocations,
  native runtime/FFI boundaries, and actual placement; do not silently truncate
  an out-of-range address. This is a proposed representation constraint, not a
  claim that the current runtime already satisfies it.
- Emit immutable, fully preinitialized objects in read-only storage. Objects
  with mutable fields or remaining runtime initialization writes stay writable.
  Retain the current writable-class-storage correction until those writes have
  actually been eliminated.

Acceptance should cover scalar and pointer fields, shared/cyclic object
references, preserved effects in mixed initializers, and serialization
round-trips. A diamond of separately compiled users must share one class-object
address and observe each other's mutations. Check ELF definitions/relocations,
address-range enforcement, native execution, emulator execution, and the full
Chapter 25 suite, including `make -j 4 tests`.

## Larger or entangled changes: defer

| Group | Eventual home | Why not in the small queue yet |
|---|---|---|
| Conditional Store and array Load control | Memory/arrays chapters | Reproduce under the earlier alias model before extracting fixes from the new memory implementation. |
| SCCP dependencies, function revival, reachability | 24; some foundations may fit 18 | Separate old-IR corrections from new Guard/Escape/BulkMemPhi and external-caller machinery. |
| TypeScalar, numeric modes, guards, symbolic fields, open/forward types | Revisit earlier homes later | A connected incomplete-types architecture, including phase ordering and errors. |
| BulkMemPhi/MemPhi, private constructor memory, allocation helpers | Revisit memory / constructors / methods | Move invariants and regressions together. |
| Serialization, global identity remapping, module escape summaries | Separate compilation | Remain with modules. |

## Register allocation: completed chapter progression

The allocator review is complete through Chapter 25. Each README ends with the
introduced technique, measured program cohorts, and commentary. `make spill-stats`
reports actual retained moves, per target and cohort, with assertions enabled.

| Chapter | Additional technique | Final cohort sizes |
|---|---|---|
| 20 | Basic coloring, rematerialization and loop splitting, with shared legality/progress fixes | 39 |
| 21 | Conservative copy coalescing and adjacent-copy forwarding | 39, 52 |
| 22 | Stronger copy-chain/backedge bias and cheap-spill ordering | 39, 52, 26 |
| 23 | Group popular single-def uses by compatible register classes | 39, 52, 26, 30 |
| 24 | One cold-only attempt for loop-Phi self-conflicts, then mandatory fallback | 39, 52, 26, 30, 67 |
| 25 | Existing area/cost ranking and later conflict strategies | 39, 52, 24, 30, 67, 14 (ten historical entries currently fail) |

Shared corrections include RegMask/LRG bookkeeping, null use masks, kills without
an output LRG, self-conflict splitting, compatible rematerialization, clobber-aware
copy reuse/bypass, deterministic ordering, and side-effect-free diagnostics. The
final audit adds the missing narrow x86 masks in 20-21 and RISC-V mask in 21;
fixed-neighbor color bias in 20-25; and ARM memory register-bank selection,
float opcodes, and matching emulator fixes in 21-25.

Chapter 25's additional fixed-use/deep-side splitting thresholds remain there.
The def-use-count snapshot and null-use guard support its extra split branch;
20-24 do not have that branch. Earlier kill handling subtracts killed registers;
25 can choose to split cloneables earlier. No independent earlier-chapter
correctness failure was established for these strategy differences. They are
not uncompleted promises to copy the complete allocator backwards. Compilation-
unit ownership and void/metadata handling also stay with the later representation.

The final ARM failure was substantive: the original Newton float programs reached
the allocation round limit because a float store demanded a GPR; the deeper loop
split kept cutting the wrong portion of the range. Permitting the supported FP
registers removes that conflict. Encoding must then use the allocated register
bank, with five-bit register numbers, for both immediate and indexed loads/stores.
The emulator must preserve floating bits and scale double offsets by eight.

Chapter 25 freezes the 212 earlier compilation entries in
[documented source fixtures](../chapter25/src/test/java/com/seaofnodes/simple/spill/README.md).
Constructor/library adaptations change IR, so this is a program-cohort comparison,
not identical machine graphs. Those rows replay allocation and legality checks;
current Chapter25Test native checks and a fresh system-library encoding contribute
ten more allocations. Earlier audit total, before adjacent-copy forwarding: **2,597 moves / 5,579 loop-weighted moves** over 222
compilations. Chapter 25's area/cost implementation is retained. Substituting the
earlier ranking saves five moves on the clients but fails a fresh `sys` allocation
at the eight-round limit; a failed library cannot be omitted from the comparison.

The earlier tables are refreshed where the final mask fixes changed them:
Chapter 20 was 238 / 357 before the subsequent lazy-memory port (now 234 / 360);
Chapter 21 was 840 / 1,456 before lazy memory and adjacent-copy forwarding.
The audit's coalescing on/off comparison saves
230 / 433 moves in Chapter 21 with identical legality fixes. Current totals for 21-24 appear in the adjacent-copy record above. Full details and comparison limits belong in
the individual READMEs rather than a second evolving set of tables here.

## Review and test protocol

1. Establish the destination baseline. Reproduce a real failure with a small
   source or constrained machine graph before classifying a change as a bug fix.
2. Apply the smallest correction, propagate it to all affected snapshots, and
   run each snapshot's focused checks and full `make tests`. Respect the current
   user-authorized review boundary; do not push without explicit instructions.
3. Force Java rebuilds when older Makefiles miss dependencies. Rebuild `sys.o`
   after compiler changes; include a fresh library compilation in allocator
   measurements rather than relying on an up-to-date object.
4. Keep legality/progress/runtime checks separate from spill expectations.
   Eight-round exhaustion is a failure; neither increasing the cutoff nor
   changing a golden proves it fixed. Preserve test membership, targets/ABIs,
   and fixed seeds. Do not sweep optimizer seeds for routine backend validation.
5. Report aggregate and local changes. Use same-compiler heuristic comparisons;
   changed frontend/lowering graphs are not allocator-only measurements.
6. Keep edited text LF-only, preserve unrelated changes, and run `git diff --check`.

The top-level runner accepts explicit chapter lists, e.g.
`make -k tests CHAPTERS="chapter20 chapter21"`. Tests in 25 alone are insufficient.

## Validation record

- **Chapter 21 lazy memory and encoding.** Baseline: **412 tests plus the
  fuzzer wrapper**. Final: **431 tests plus the fuzzer wrapper**. Make release
  and spill-stats pass. The 16 forwarded memory checks retain scheduling and
  allocation coverage; three execution/synthetic-exit tests add 42 native x86
  and 84 ARM/RISC-V evaluations with independent expected results, plus checks
  of no-exit loop memory through scheduling. **9,800 scheduled evaluations**
  pass across 100 optimizer seeds. Emulated multi-function programs enter at
  main's encoded offset, which need not be zero.
  The unchanged **91-entry** spill cohort changes **840 / 1,456** retained /
  weighted moves to **845 / 1,559**. RISC-V BrainFuck changes 35 / 42 to
  37 / 58 in the Chapter 20 cohort and 35 / 42 to 46 / 130 in Chapter 21.
  An ablation omitting only read-before-New ordering returns those rows to
  35 / 42 and 37 / 58; the ordering is retained, with no allocator tuning.
  All eleven changed rows are recorded in the chapter README.
  Logs: `build/memory21-baseline.log`, `build/memory21-final.log`,
  `build/memory21-seeds.log`, `build/memory21-spills-baseline.log`,
  `build/memory21-spills-final.log`, `build/memory21-spills-ablation.log`,
  and `build/memory21-release.log`. Chapters 22+ are unchanged for review.

- **Chapter 20 lazy memory and register allocation.** Baseline: 380 tests plus
  the fuzzer wrapper. Final: **396 tests plus the fuzzer wrapper**, with the
  15 forwarded regressions and one allocation regression spanning all three
  targets. **9,800 scheduled runtime evaluations** pass across 100 optimizer
  seeds. **42 selected/scheduled/allocated graphs** pass legality checks at fixed
  seed 123 on x86-64, RISC-V, and ARM. Make release and spill-stats pass.
  The unchanged 39-entry spill cohort changes from **238 / 357** retained /
  weighted moves to **234 / 360**. The x86 array prefix sum changes 5 / 5 to
  6 / 13 because selected-read ordering adds a loop move; a scratch ablation
  omitting that ordering returns to 5 / 5. No allocator heuristics or benchmark
  membership changed. The chapter README records all six changed program rows.
  Logs: `build/memory20-baseline.log`, `build/memory20-final.log`,
  `build/memory20-seeds.log`, `build/memory20-machine.log`,
  `build/memory20-spills-baseline.log`, `build/memory20-spills-final.log`,
  `build/memory20-spills-ablation.log`, and `build/memory20-release.log`.
  This checkpoint was reviewed and committed as `f01ee052`.


- **Chapter 19 lazy memory and instruction selection.** Baseline: 360 tests
  plus the fuzzer wrapper. Final: **375 tests plus the fuzzer wrapper**; release
  jar builds with Make alone. Forwarded 13 memory regressions and added two
  instruction-selection checks: alias/Phi preservation and allocation/call slots
  on both targets, and folded x86 reads ordered before clobbering Stores.
  **9,800 scheduled evaluations** and **2,800 selected/scheduled graphs** pass
  across 100 optimizer seeds, with x86_64_v2 and riscv selection exercised.
  The latter checks graph scheduling and shape; this chapter does not execute
  encoded machine code. Logs: `build/memory19-baseline.log`,
  `build/memory19-final.log`, `build/memory19-seeds.log`,
  `build/memory19-machine-seeds.log`, and `build/memory19-release.log`.
  Chapter 14's inherited And graph factors here without widening; its runtime
  assertion remains. This checkpoint was reviewed and committed as `612555ee`.


- **Lazy memory through 17 and 18.** Baselines: 17 has 288 tests; 18 has 318
  plus its fuzzer wrapper. The port passes **298 tests in 17** and **331 tests
  plus the fuzzer wrapper in 18**. Both carry the nine memory regressions and
  constructor regression; 18 adds checks for non-inlined calls, recursive heap
  updates, and allocation inside an inlined function. The non-inlined check
  also verifies Call's input count and last-slot function pointer after GCM.
  **7,700 / 9,800 evaluations across 100 optimizer seeds** pass in 17 / 18,
  checking final memory values, complete scheduling, partial New inputs, and
  two-result New. Full suites and both release jars build with Make alone.
  Logs: `build/memory17-18-baseline.log`, `build/memory17-18-final.log`,
  `build/memory17-seeds.log`, and `build/memory18-seeds.log`.
  Chapter 17's And graph remains unfactored when the result would widen; the
  existing runtime assertion is unchanged. Shared allocation memory retains
  the `.y` Load in its inherited array example, as in 16. Nullable diagnostics
  select the earlier array dereference in 17/18. The Chapter 17 null test now
  makes `p2` mutable so its setup does not fail first on an unrelated final-field
  store. This checkpoint was reviewed and committed as `2d51b647`.

- **Chapter 16 lazy memory and constructors.** Baseline: 237 tests. The port
  passes 247 tests, including nine carried memory regressions and one constructor
  case combining reads, writes, a loop, and an unrelated alias. Full Make tests
  and release builds for 10a-16 pass: **1,568 tests across eight snapshots**.
  An additional **7,700 evaluations across 100 optimizer seeds** check memory
  behavior, complete scheduling, two-result New, and partial allocation inputs.
  Logs: `build/memory16-baseline.log`, `build/memory16-all-tests.log`, and
  `build/memory16-seeds.log`.
  The changed graph order exposed a dependency notification gap in
  `Chapter14Test.testCloneAnd`: replacing Minus#25 rewired And#28 without waking
  its dependent Phi#26. `Node.subsume` now wakes each rewired user's recorded
  dependents in 10a-16. The existing test passes without weakening its assertions.
  Chapters 17+ are unchanged; constructor integration is the review boundary.

- **Generalized Phi factoring, 10a through 15.** Replaced binary-only factoring
  and the blanket memory exclusion with operand-wise Phis and exact-class
  `copyEmpty`. Matching Loads and sole-use Stores are eligible when unbound to
  control; operation attributes, field/alias identity, operand types, and
  result-type monotonicity are preserved. Load safety examines memory users
  before explicit GCM anti-dependencies exist. The final guard checks only
  immediate users, stopping at Stores, Phis, MemMerges, and New in 15. Memory factoring waits for bulk
  partitioning at its Region; MemMerge and BulkMemPhi are never factored.
  Stores bound to control in 11-14 remain excluded. User removal now wakes
  recorded dependents, and empty-diamond folding observes projection rewiring.
  Three regressions live in each snapshot's `Chapter10Test`; the original 10a
  Phi fails both positive folding checks, and disabling the Load safety guard
  exposes an invalid read/write schedule in the negative probe.
  Full Make tests pass **159 / 162 / 177 / 181 / 196 / 216 / 230** and all seven
  release jars build. A 10a Phi label and one 14 Region ID were refreshed;
  expression/value expectations remain unchanged. Each snapshot also passes
  **3,500 evaluations across 100 optimizer seeds**, and 15 passes another
  **7,000 allocation/memory evaluations**. Logs: `build/phi-drop-baseline.log`,
  `build/phi-drop-tests.log`, `build/phi-drop-seeds*.log`,
  `build/phi-drop-negative.log`, `build/phi-drop-antidep-negative.log`, and
  `build/phi-drop-allocation-seeds15.log`. Chapters 16+ are unchanged.
  The subsequent guard refactor moves memory eligibility into virtual
  `MemOpNode.canDrop` overrides and clobber inspection into Load. Removed 10a's
  unused visited set. All 1,321 tests and seven release builds passed
  (`build/phi-guards-refactor.log`). At Cliff's request, the subsequent
  simplification also removes recursive aggregate traversal from 10b-15:
  a one-step check conservatively stops at possible indirect clobbers,
  keeping the teaching cost proportionate to saving one Load. The aggregate
  example now keeps two Loads in 10b-15; its runtime checks remain unchanged.
  All 1,321 tests and seven release builds pass with the simpler guard
  (`build/phi-guards-one-step.log`).

- **Chapter 15 partial allocation memory (review checkpoint).** Implemented the
  approved two-result New and carried the lazy memory model through parser,
  scheduling, evaluator, viewer roles, and chapter documentation. Full Make
  suite passes **227 tests** and the release jar builds, up from baseline **218**: six carried memory
  regressions plus three allocation regressions. They cover unrelated aliases,
  mixed integer/float/pointer contents, reads across allocation, and allocation
  inside loops. A disposable probe also passed **7,000 evaluations across 100
  optimizer seeds**, including empty structs, arrays, both slot conventions,
  partial coverage, and complete scheduling of live definitions. Region/Loop
  golden changes only alter IDs; the nullable-access diagnostic matches 13-14.
  Logs: `build/memory15-baseline.log`, `build/memory-forward-15.log`, and
  `build/memory15-seeds.log`; final tests and release: `build/memory15-final.log`.

- **Memory forward port through Chapters 12-14.** Preserved
  floating-point operations, typed reference initialization, and narrow stores
  while forwarding the reviewed parser, memory nodes, GCM, Return slots, and
  evaluator. Each chapter inherits all six memory regressions. Make tests pass
  **178 / 193 / 213**, up from baselines **172 / 187 / 207**; all three release
  jars build. Updated printed Region/Loop IDs without changing expression
  expectations. The invalid `head.next.i` test in 13 and 14 now reports the
  possibly-null `i` access first; both accesses remain invalid, and the test
  still requires rejection. Logs: `build/memory-forward-baselines.log` and
  `build/memory-forward-12-14.log`. The subsequent allocation port is recorded above.

- **Control/memory slot convention (10a, 10b, 11).** Return now takes
  `{ctrl, $mem, result}` and describes its result tuple in that order, with
  whole memory typed as `TypeMem.BOT`. Evaluators use `expr()` rather than a
  literal result index. Updated viewer roles, existing projection checks,
  documentation, and numbered diagram edges. Make tests pass **156 / 159 / 174**
  respectively, and all three release jars build. Log:
  `build/return-memory-slots.log`. Preserve control slot 0 and memory slot 1
  when forwarding Call, CallEnd, and other nodes in later chapters.

- **Chapter 11 memory forward port (2026-09-29, review checkpoint).** Parser,
  Start, Scope, and Return now use 10b's one-memory model and its three memory
  nodes. GCM schedules MemMerge as a dependency node, filters both Load
  readiness and anti-dependencies by alias, and retains the full Store placement
  range needed by the evaluator. Synthetic never-taken loop returns remain
  scope-free. Updated the evaluator, viewer Return edge roles, walkthrough
  diagrams, and scheduling code excerpts. The baseline passed **168 tests**;
  the port passes **174**, including all six forwarded memory regressions.
  Fifty existing printed expectations changed only Region/Loop node IDs.
  Five memory programs pass seven inputs across 100 optimizer seeds, with
  scheduling assertions and final heap/value checks (**3,500 evaluations**).
  Make also builds the release jar and regenerates all six updated walkthrough
  diagrams; `git diff --check` passes.
  Logs: `build/memory-forward-11-baseline.log`, `build/memory-forward-11.log`.
  Chapters 12-24 are untouched pending review.

- **Chapter 10a/10b split (2026-09-29).** Replaced `chapter10` with two
  independently buildable snapshots: whole-memory SSA in 10a, then lazy
  `MemMerge`/`MemPhi`/`BulkMemPhi` partitioning in 10b. Updated Make discovery,
  Maven modules, IDEA descriptors/local module list, chapter navigation,
  linear-history link handling, and regenerated the memory diagrams.
  Fresh Make builds pass **156 tests in 10a and 159 in 10b**, including the
  chapter-local fuzzer regression wrapper. Both release jars and tags build.
  Offline Maven tests pass in both modules. Five memory programs each pass
  seven inputs across 100 optimizer seeds in each compiler (**3,500 executions
  per chapter**). Seed 97's missed bulk-Phi revisit is retained as a regression.
  Logs: `build/chapter10-clean-build.log`, `build/chapter10-maven.log`,
  `build/10a-seeds.log`, and `build/10b-seeds.log`. At this checkpoint Chapters
  11-24 had not yet adopted the new memory representation; forward-port progress
  is recorded above.

This is a condensed completion record, not a list of current suite counts for
old revisions. Earlier detailed traces are in Git history; durable invariants
have been consolidated into the [AI notes](../skills/chapter25-codex-notes.md).
Unresolved reproductions have been promoted to the pending queue above.

On 2026-09-28, the constructor/memory-check backport passed the full Make suites
in every compiler snapshot from 10 through 24, including their default fuzzer
wrappers (`build/error-backport-tests.log`). The destination baseline was also
green (`build/error-backport-baseline.log`). The arithmetic contract experiment
and its separate validation are recorded independently.

| Completed work | Scope and evidence retained |
|---|---|
| Narrow C integer returns | On 2026-09-28, instruction selection in 22-25 sign/zero-extends direct C call results according to the declared 8/16/32-bit return type. x86 uses MOVSX/MOVZX/MOVSXD or a 32-bit MOV; ARM uses SBFM/UBFM; RISC-V uses shifts, including zero-extension of its sign-extended u32 ABI result. Chapter 25 consults external function identities because optimization can replace ExternNode with an ordinary constant. Native regressions run both x86 calling conventions with garbage upper return bits; ARM/RISC-V stubs exercise signed/unsigned boundaries and unchanged i64 results. Chapter 25's errno test now also requires `close(-1)==-1`. Original selectors reproduce failures in 22 and 25, including the libc case (`build/c-return-negative-{22,25}.log`). Full Make suites passed: 22 (433), 23 (455), 24 (485), 25 (479), plus each default fuzzer wrapper; 25 rebuilt sys.o (`build/c-return-final2.log`). |
| ARM bitfield emulation | SBFM/UBFM decoding now handles both immediate fields, covering signed/unsigned extracts as well as ASR/LSR/LSL. The previous emulator treated all SBFM as ASR and all UBFM as LSL, hiding a logical-right-shift error and rejecting the new extension results. Introduced the correction and independent instruction-word regression in 21 and forwarded through 25. Full 21 suite passed (412 tests plus fuzzer; baseline 411); 22-25 validation is included above. |
| FunPtr lifetime and callable entries | On 2026-09-28, introduced Return-linked FunPtrNode in 22-24, including semantic constant folding. Direct call targets retain existing call-graph treatment; all other address uses conservatively retain unknown callers, without escape analysis. Stop retains each callable Return through instruction selection; machine operands omit the lifetime edge. Anonymous/library returns failed relocation on all three targets before the change; 24 also failed named returns. New tests call returned anonymous/named/library pointers on ARM/RISC-V and from native C, and check discarded anonymous bodies disappear. Full Make suites passed: 21 (411), 22 (430), 23 (452), 24 (482), each plus its fuzzer wrapper. Baselines passed at 410/427/449/479. Chapter 24's Phi-selected functions retain general parameter types; graph expectations reflect that conservative rule. |
| Function-address backend and worklist corrections | RISC-V TFPRISC now patches ADDI at opStart+4, rounds AUIPC for signed low bits, and reports its actual eight-byte size (21-24). ELF export emits anonymous bodies without exported names (21-24). Actual invocation exposed both bugs after retention was repaired; Chapter 21's reduced conditional-return probe trapped before the encoding fix. Chapter 22 gains the later inlining dominator null guard. In 24, killing Call#618 reduced FunPtr#540 to one use without waking dependent CallEnd#560; Node.kill now wakes the remaining definition's dependencies after removing a use (24-25). The no-return String workload loses unused hash bodies; its spill expectations become zero on all targets. Chapter 24 spill-stats passes all 212 original compilation entries at 1,351 moves / 3,115 weighted moves; its README separates this measurement from the original allocator ablation. Full Chapter 25 Make validation also passed: 476 ordinary tests plus its 17-seed fuzzer wrapper, with sys.o rebuilt. |
| Chapter 25 direct C data bindings | On 2026-09-28, full `make tests` passed after the Field/ExternOffset refactor: 476 ordinary tests plus the fuzzer wrapper (17 seeds). Coverage checks zero layout space for external fields, native shared integer/float storage across C calls, compound updates, source/precompiled imports sharing aliases and preserving storage metadata, ARM/RISC-V emulated address relocation and signed loads, and the errno accessor after a failed native call. Direct bindings remain in 25: 22-24 have different singleton-pointer constant and instruction-selection contracts. Serialization retains magic C0DE; the changed layout requires rebuilding all objects. Only the independent RISC-V fixes below were selected for backport. |
| RISC-V right shifts and pointer relocation | Backported from 25 to 22-24 on 2026-09-28. EvalRisc5 selects arithmetic/logical right shifts correctly; TMPRISC rounds the AUIPC high part to account for signed ADDI low bits. New regressions cover immediate/register shifts and forward/backward relocation boundaries. Both fail before the Chapter 22 fixes (arithmetic shift becomes logical; delta 0x800 lands 4 KB low). Full Make suites passed: 22 (427), 23 (449), 24 (479), each plus its fuzzer wrapper. Baseline suites also passed (425/447/477). |
| Chapter 25 Load fuzzer recheck | At `8212a66f`, seed `-4628356252269023530` passes its direct fuzzPeepsRegression replay (`build/load-seed-recheck.log`). The complete `make fuzzer` wrapper also passes all 17 seeds on 2026-09-28 (one JUnit wrapper test). The seed remains a regression; OPEN_FAILING_SEEDS is empty. No new compiler fix was needed. The older Load TODO report is superseded. |
| Arithmetic high results at the SCCP boundary | Chapter 24 integer and float arithmetic now return global TOP for high operands, matching 25. Widened ArithNode.compute's Java return signature to Type; no caller requires TypeInteger. Retained the typed integer fallback for unresolved/error operands, for the parser dependency in the pending queue. The TOP change starts with SCCP in 24; earlier snapshots retain their typed contracts. Full Chapter 24 Make suite passed: 477 tests plus its fuzzer wrapper (`build/arithmetic-top-full-24.log`). |
| Required-field and memory-check backport | Chapters 16-24 retain allocation-time checks for inline `new S { ... }` initialization. Chapter 17 had its TOP/unset-field check commented out; restored it. Expanded the existing missing-initializer regression through 24 (introduced its array-field cases in 16), and forwarded the known-null load regression to 10-24 using their existing parse-time diagnostic. Added the fresh-allocation zero-fold monotonicity assertion in 19-24 and the TOP-pointer store-diagnostic guard in 18-23 (already present in 24). Before 18, TOP pointers already produce a diagnostic before the store's pointer cast. Chapter 25's constructor-exit snapshots and implicit-constructor declaration check stay with its separate allocation/constructor model. Its CallEnd return and MemMerge load-dependency corrections already have equivalent behavior in the applicable earlier implementations; storage-type nullability machinery stays with 25. Global error-result type changes are paused in the queue above. |
| Chapter 25 required constructor fields | Parser records each explicit constructor's merged exit field types by Var identity and checks them against the complete field list when the struct closes. With no user constructor, declaration defaults are checked as an implicit empty constructor. Never-returning constructors have no completion obligation; nested blocks retain constructor context. Declaration-body assignments to required fields now emit their initializing stores. Load's fresh-allocation zero fold is restored with a monotonicity guard. Existing constructor tests cover partial/early returns, shadowing, later fields, valid defaults and non-returning constructors; older unused-struct/String fixtures now provide constructors, with measured spill expectations updated. Seed `6506797708065910879` is rejected during parsing and passes its regression. On 2026-09-28, all 472 non-fuzzer tests passed across `build/ctor-check-tests.log` and `build/ctor-remaining-tests.log`; the full Make run remains red solely on the independent Load TODO seed above. Allocation-time initialization checking was lost in the August 10 rewrite (`51f1f8f4`); this restores the obligation in the parser without depending on optimized private memory. |
| Chapter 25 null-dereference diagnostic | Existing `Chapter10Test.testNullGuardErrors` includes `if (p == null) return p.x; return -1;`. The earlier recursive constant-fold error guard was removed by Cliff in favor of erroneous nodes computing BOTTOM, with related eager-folding and monotonicity corrections. The earlier full-suite pass in `build/null-deref-arith-top.log` applies to the superseded guard implementation, not the current edits. The exposed uninitialized-field failure is fixed by the constructor checks recorded above. |
| Chapter 22 String without an explicit return | Backported 23's default-main teardown: when top-level control falls through, replace main's Start input with XCTRL and queue its users. Previously 22 omitted the return while retaining live control, leaving loop fragments with missing exits. Current seed 0 reproduced a missing-successor failure in Encoding; the earlier GCM changes had moved it past the originally recorded failure. The unchanged `Chapter20Test.testString` now also checks seed 0 through Encoding on x86/RISC-V/ARM, preserving its existing seed-123 allocator checks. Two Chapter09Test graph expectations now reflect removal of a default main with no live return; their sources are unchanged. Baseline and final full Chapter 22 suites passed (425 tests plus fuzzer), logged in `build/string-baseline.log` and `build/string-final.log`. Chapters 23-24 already contain this teardown; no later compiler changes were needed. |
| Infinite-loop evaluation | Eval2 now runs the missing loop-tree phase in 21 and explicitly follows NeverNode projection 0 in 21-25. Existing `testFcn9` expects timeout in 21-24. Full 18-25 Make suites passed on 2026-09-26 (`build/never-exit-tests.log` and `build/never-exit-final.log`). On 2026-09-27, Cliff requested removal of the frozen-source fixture, replay tests, related reductions, and seed-list entry as unnecessary test overhead; the evaluator fixes remain. |
| Always-on peepholes | Removed the global disable flag and branches in 2-18, the Chapter 18 parse overload, test toggles, and transpiler metadata/Rust output. Updated early graph expectations and READMEs; removed duplicate no-peephole scope tests. Chapter 8 fuzzing checks compilation/evaluation for failures; 9-18 compare worklist seeds 123/456 like later chapters. The Chapter 18 Load type workaround was unnecessary with normal forwarding and was removed. All 1-25 default Make suites passed across `build/always-peeps-tests.log` and `build/always-peeps-final.log`; later evaluator corrections are recorded above. The baseline was green in `build/always-peeps-baseline.log`. Direct checks exercised both worklist seeds in 9-18. The transpiler compiles and its focused grammar/Rust-output check passes; its existing repository-wide test stops at source-free chapter00 with `no source files`. |
| B10 and integer/constant-array widening | Widening starts in 24. Both 24-25 now use levels 0-3, dual widening `3-widen`, retained singleton widening, and widening on TypeConAry. `nonZero` preserves the coordinate. The same TypeTest regression and integer gather ranges run in both snapshots; before the backport, Chapter 24 fails the widening regression and lattice associativity. Loop Phis widen only below-center nonconstant ranges: widening an above-center range to level 1 then falling to zero at level 0 violated SCCP monotonicity in 24's existing `testCloneAnd` and `testInfinite`; both now pass, with the guard also in 25. |
| TypeConAry API backport and validation | 24-25 share the reduced raw `_make(any,widen)` API, integer meet dispatch through the distinct `TCONARY` tag, and gathered samples. Preserve 24's GLB hooks and ordinary byte stream; scalar envelopes and serialization stay in 25. Chapters 22-23 consolidate gather and remove redundant wrappers/arguments, retaining their pre-widening representations and needed sentinels. Full Make suites passed on 2026-09-26: 22 (428), 23 (450), 24 (480), 25 (474), each plus its fuzzer wrapper; all include TypeTest, and 25 rebuilt `sys.o`. Logs: `build/conary-backport-*` and `chapter25/build/conary-backport-25.log`. |
| #254 subtraction zero identities | Require integer operands in 4-25 and resolved integer mode in 25. Preserve floating-point signed zero and subtraction diagnostics. `Chapter12Test.testSubZeroFloat` runs in 12-25; `Chapter22Test.testSubZeroTypeError` runs in 22-25. Against the original PR, the signed-zero test fails in 25 and the diagnostic test fails in 22-25. Full Make suites passed in every chapter 4-25 on 2026-09-25, including fresh `sys.o` compilation in 25. |
| Short type extrema | Memory, struct and memory-pointer BOT/TOP names from 10; function-pointer names from 18. Exact types use shortcuts in ordinary, nested and HTML prints; precise types retain their details. Full Make suites in 10-20 and type/frontend suites in 21-25 passed; canonical/nested print checks passed in 10-25. |
| Integer result types | Integer-op fallbacks in 4-13 and remaining unary cases through 17 retain integer types; 5's Phis meet their data-input types. Full Make suites in 4-17 passed, plus a chapter 5 snapshot check of Phi/Add types. Integer BOT starts in 4; later arithmetic already has typed fallbacks, with unresolved bimorphic modes preserved in 25. |
| B14 cyclic leaf-kind equality | 23-24 backport; 25 already correct. Cyclic equality starts in 23. `TypeTest.testCyclicLeafKinds` fails before the fix; earlier interned-child equality already checks kinds. Full affected suites passed. |
| B13 / #246 null guards | Nested Not/null guard discovery from 10; short-circuit Phi availability in 23-24. Regression and full suites in 10-25 passed. Separate Chapter 25 null-dereference diagnostic is recorded above. |
| B09 and #251 debug printing | Side-effect-free print/accessor paths from their first applicable chapters; `_` diagnostic naming, type interning allowed, layouts forbidden. Dead nodes use `isDead()`, not null `_inputs`, from 7. Original-printer negative checks and full affected suites passed. |
| #247 emulator stores | Full eight-byte writes in ARM/RISC-V emulators, 21-25. Independent byte-pattern/overwrite regressions failed before the fix; all affected suites passed. |
| B12 x86 immediate multiply | Dedicated destination-register encoder in 21; 22-25 already had it. Low/extended-register encoding regressions and full 21-25 suites passed. |
| B11 optimized return checking | Remove parse-time meet in 18-24; 18 checks the optimized result type. Earlier separate Returns already accept dead mixed-type exits. Regressions start in 10/12; full 10-25 suites passed. |
| Dominator caches | Shared searches from 6; char depth in 6-17, separate char depth/version from 18, checked overflow and preserved clone fields. Inlining invalidation in 18-20. Full affected suites passed; dedicated bookkeeping helpers/tests were removed at Cliff's request. |
| GCM, constant cloning, isPinned removal | Shared scheduling from 11, per-function cloning of stacked constants from 18; preserve chapter-specific anti-dependencies and linked Parm ownership. Full 11-25 suites passed. Keep clone-based copyEmpty and non-final graph fields per review. |
| TypeFunPtr normalization | Trailing-default normalization in 23-24; 25 already correct. Only gather cases added; lattice laws and full 23-25 suites passed. Other `7f3b8856` representations remain in 25. |
| Inlining Return types and ARM execution | Live return expressions outlast a deleted inline entry (20-25); ARM register SUB, call instructions, frame byte counts/alignment, and vector growth (21-25 as applicable). Reduced negative cases and full affected suites passed. The earlier standalone ARM String/hash failure is resolved. |
| Native test reliability | Full process exit status, stdout plus successful execution, and distinct concurrent HelloWorld artifact names. Crashes no longer pass merely because output matches or an exit status narrows to zero. |
| Allocator progression 20-25 | Fixed cohorts, legality checks, native/emulated execution from 21, and same-compiler comparisons. Final results below. |
| Build/fuzzer harness baseline | All 25 chapter targets passed after build setup and explicit seed lists. Empty default lists were not exploratory fuzz coverage. |

Final allocator audit, 2026-09-20, Windows/Cygwin with assertions:

| Snapshot | Full suite | Spill report |
|---|---|---|
| 20 | 378 tests + fuzzer | 39 compilations, 238 / 357 |
| 21 | 408 tests + fuzzer | 91 compilations, 840 / 1,456 |
| 22 | 422 tests + fuzzer | 115 compilations, 838 / 1,496 |
| 23 | 444 tests + fuzzer | 145 compilations, 933 / 1,745 |
| 24 | 473 tests + fuzzer | 212 compilations, 1,332 / 2,956 |
| 25 | 468 tests across parallel groups, including fuzzer | 222 compilations, 2,597 / 5,579 |

All full suites and final spill reports passed. The Chapter 25 run rebuilt
`sys.o`. Negative checks against saved original classes reproduce fixed-neighbor
bias, narrow-store masks, and ARM floating-memory encoding failures. The original
Newton ARM allocation also failed before the fix. The Chapter 21 coalescing-off
comparison passes all allocation/runtime checks but reports nine changed spill
goldens, intentionally exiting nonzero. The Chapter 25 earlier-ranking ablation
fails fresh library allocation, so its client-only totals are explicitly partial.
Logs and saved original classes: `chapter{20..25}/build/regalloc-final/`.