summary_l3_selfhost_fix.md 4.4 KB

L3 option 2 — self-host root cause and fix (2026-10-05)

Branch ast-stage-c. This supersedes the "size/complexity-triggered" conclusion in docs/handoff-l3-option2.md.

Result

  • t_lower3 byte-matches; suite 184/184 (with lower_ok t_lower3).
  • Self-host green: FIXPOINT OK (stage2.ssa == stage3.ssa, 3,184,888 bytes).
  • Zero SymTab surface; no FORWARD; no SymTab edits.

Root cause of the self-host SIGABRT

Not size/complexity. It is a deterministic codegen bug:

A record's array field is stored as an inline descriptor ({ l <len>, data }), but in static array-of-record data the record elements are emitted as a single zero blob (z n*size), so the embedded length word is never initialized (stays 0). Passing such a field as an open-array actual makes the callee read length 0 and trap (call $abort).

Bisect evidence: m2s2 aborts in Lower.CopyN ← NoteProc ← ProcHeading while parsing Lower.mod. Minimal repro docs/wip/arrrec-field-openarray.mod (procs[i].name → VAR dst: ARRAY OF CHAR): gm2-built compilers work, V3-built ones abort (rc 134). Shrinking MaxSym/MaxProc did not help because the bug is not size-related.

Option 2's procs : ARRAY OF ProcEnt table hit exactly this pattern (procs[i].name, procs[i].pname passed to CopyN/CopySpan).

Fix landed

Flatten Lower's procedure table into parallel flat arrays — no ARRAY OF RECORD at all:

  • procName (stride 64), procUid, procRes, procDep, procExt, procNpar, and parTy/parVar/parName (strides MaxPar, MaxPar*64).
  • NoteProc/NoteParam write via CopySpan into the flat arrays; FindProc/ProcInfo/ParamInfo read them.

Flat top-level arrays are descriptor-backed with a correct length word, so no record field is ever passed as an open array. This matches the handoff's "no arrays of records in Lower" rule (which the earlier design did not actually satisfy).

General codegen fix — landed (hybrid)

The general fix (option 1 below) was landed: QbeGen.ArrBodyItems expands record/class array elements per element when the element (recursively) has a nested-array field, with ArrData emitting the matching per-element sub-descriptors, so every inline array field's length word is in the static data.

To stay inside QbeGen.sessBuf (4 MiB), it is a hybrid: only small record arrays (data ≤ 131072 bytes) are expanded per element; large ones (e.g. AST.arena, 65536 NodeRec) stay a compact zero blob, and their elements' inline lengths are set at run time by a generated init loop (QbeGen.DeclRecTemplate + QbeGen.InitRecArray, driven from Lower). Plain record arrays (no nested-array field) stay a compact zero blob, so AST.arena does not balloon.

Fixed and covered by compiler/tests/t_arrrecfld.mod (small), t_arrrecbig.mod (large), t_nestarr.mod (nested) and t_recarr.mod — all run green, and docs/wip/arrrec-field-openarray.mod / docs/wip/nested-array-field-bug.mod now compile and exit 0 / 42. No open codegen case remains here.

Latent fragility — root-caused (2026-10-19)

The old note here said removing ASTDUMP from M2.atg broke the fixpoint and was a symptom of a "parser-size-sensitive" miscompile. That was a misdiagnosis. ASTDUMP dumped the whole AST to stdout; when the compiler was built from its own sources that dump contained the string literal "Parsed correctly" (the driver's success message), and the fixpoint's M2make gate was grep -q Parsed — so the gate passed even when the compiler session was reporting Incorrect source. Removing the dump removed the masking line, and the pre-existing session errors surfaced — not a size effect. The errors were real: Lower.mod called its mutually recursive walkers before their declarations (invalid call, codegen dropping the calls), plus the FormalOk open-array comparison bug.

All fixed in a3ef897: ASTDUMP removed, the gate tightened to grep -q 'Parsed correctly', Lower's walkers declared in Lower.def, FormalOk corrected. The gate now fails on any session error (verified: a data-order violation makes M2 print Incorrect source and the gate fails), and the fixpoint is byte-identical across the four size-changing commits of 2026-10-19.

(The separate record-array-field / open-array codegen issue is described above and is fixed — that was a distinct bug from the ASTDUMP one, resolved by the hybrid ArrBodyItems/InitRecArray fix.)