summary_lower_l7.md 4.5 KB

Lower phase — L7 (the flip): status and plan (2026-10-05)

Branch ast-stage-c. L7 = "flip: lower from the AST instead of inline; delete the inline emit + Fwd* machinery".

Why L7 is not incremental

The flip makes Lower the sole emitter. The obstacle is that QbeGen holds one session state, used by the inline emit during parse; a per-unit Lower emit would be clobbered by the next unit's inline emit. So the flip is a big-bang: either remove/suppress the grammar's 533 QbeGen.* call sites, or give QbeGen two states — neither is a byte-verifiable step.

The pragmatic path is therefore a session re-emit: collect every unit's AST and re-emit the whole session with Lower after parsing. That is byte-comparable against the legacy session image and becomes the flip once the inline emit is removed.

Started (d30abfd, tag v3-lower7-scaffold)

  • Lower.LowerSession/LowerOneUnit/MaterializeGlobals.
  • Driver (compiler.frm) collects GetUnit() per file and calls Lower.LowerSession under -lower.
  • SymTab.Materialize makes a unit's own VAR/CONST visible in globScope, so GlobalRef resolves "<unit>_<name>" at session end (the unit scopes are closed by then).
  • Removed Lower.Reset from the M2 root so the owned tables persist across a session.

Still scaffolding (inline emit untouched); suite 200/200, FIXPOINT OK (3,401,129 bytes).

Progress (ce2fc67, tag v3-lower7-session)

  • DefUnit now builds an AST (proc headings recorded as NkProcDecl); LowerDecls replays a bodyless proc as a heading.
  • Session re-emit works for small sessions: lower_ok_files added, and DBasicProg, DQualProg, TQual (def-CONST), TQualP (def-PROC) byte-match. Fixes: image named after the program module; MaterializeGlobals also materializes TYPE/CLASS names; SymTab.ReenterModGlobal keeps nested-module globals' "<outer>_<name>" symbols; AST.Init once per session under -lower.

Suite 204/204; FIXPOINT OK (3,405,813 bytes).

Remaining gaps

  1. AST-arena size (fundamental). LowerSession needs every unit's AST alive at once, but AST.MaxNodes = 65536 is sized for one unit; the compiler's session overflows it (MakeNode → NoNode, later units dump (nil)). Growing the arena past the 4 MiB sessBuf is not viable. So the session re-emit cannot handle the compiler — the flip must instead emit per unit at each unit's END (AST valid, scope open), which is the two-state / inline-emit-removal problem.
  2. External procs. storage_prog differs: Lower emits $malloc_6(l link, ...) where the legacy emits $malloc(...) — the ext flag in Lower.NoteProc is hardcoded FALSE and Mangled's ProcLink fails after scopes close.
  3. Definition-module proc replay (done), nested globals (done).

The "hang" was a Lower bug, not a miscompile

LowerDecls emitted a procedure's parameter loop even when ProcInfo failed, using an uninitialised np — so the loop ran ~forever inside FindProc. (MaxProc = 64 is far too small for a whole session, so ProcInfo failed for most of the compiler's procs.) Fixed (a9a8ecd): the proc is skipped unless ProcInfo succeeds, and each ParamInfo is guarded. Also grew the AST arena/pool (caeeeb5) — the arena/pool data lines are compact z runs, so this is nearly free.

Hash index attempted — also trips V3 miscompiles

Implemented an open-addressing name index (HashName/HashGetSym/ HashGetProc) for FindSym/FindProc in Lower. Results:

  • With the index, the session re-emit on the compiler's sources became fast (MaxProc=512 completed in <300 s; before it hung).
  • But the tables must hold the whole session (the compiler has 713 functions, so MaxProc ≥ 713), and at MaxProc ∈ {768,1024} the same run is slow/segfaults (CopySpan via ParamInfo) — the size/layout-sensitive bug again.
  • Worse, the hash itself makes the self-host fail: with the index in Lower, m2s2 (built by m1s1) produces no output on the compiler input (stage 3). So the index triggers another latent V3 miscompile.

Reverted to the green state (a9a8ecd). Suite 204/204.

Conclusion

Three separate latent V3 bugs now block the whole-session re-emit: (a) the ProcInfo garbage-np loop (fixed), (b) a size/layout-sensitive miscompile at large tables, (c) a miscompile triggered by the hash code. Until (b)/(c) are isolated, the session re-emit is impractical, and the grammar rewrite remains the more predictable flip path.