summary_lower_l7_complete.md 4.8 KB

Lower phase — L7 complete (2026-10-08)

Branch ast-stage-c. Tag v3-lower7-complete.

This closes L7: Lower is the only emitter, the inline emit that could be removed cleanly is gone, the -lower scaffold is retired, and dead code is swept. Verified with compiler/run_tests.sh (181/181) and bootstrap/fixpoint.sh (FIXPOINT OK) after every step.

What happened, in order

1. The switch is flipped (857c221)

The driver (compiler/src/compiler.frm) always runs the per-unit Lower emit (BeginSession/BeginUnit/LowerUnitNow/EndSession); the grammar's inline emit runs suppressed (SetNoEmit) and is discarded. A no-op flip, because Lower had been verified byte-identical to the legacy inline emit on every single-file test and every session.

2. Inline emit deleted from statements and declarations

e5830b2 (builtin statements), ce37247 (control flow), acc4e91 (declarations/lifecycle). Not mechanical: TopLoop/PushLoop/ TopWith/PushWith/IsImm/Fold2/LocFull/ConstInt are checks or parser state and stay.

3. A real bug found and fixed (7de78bb)

Removing the grammar's QbeGen.DeclVar exposed a latent mis-resolution (Strings.Replace's src VAR actual became $Strings_nl). QbeGen.AddrOfVal scans the whole VAR-actual note ring, and RestoreSession restored nn but never cleared the ring — so the grammar's suppressed parse-time NoteAddr entries survived into Lower and a stale key could false-match. Fix: clear the ring in RestoreSession. Output-neutral on Showcase11; corrected some latent resolutions in the compiler's own image.

4. Dead-code sweep

d730e80 (the Can* family, LowerSession/LowerUnit, SetLower/lowerRequested), e269828 (QbeGen orphans), 241c2c9 (SymTab/QbeGen orphans), b0ef2bf (AST.TxtLen/SetKind), b4d9e25 (post-decoupling orphans: SetOutName, FwdDesignator, FwdAddrOper, IsExternal, MethUid, FieldOwner). A tree-wide scan (tests included) now reports 0 unreferenced procs.

5. Value/emit decoupling

The expression value is also the front-end constant evaluator (Expr's q feeds SetSymVal/NoteConstVal/IsImm/ConstInt), so it could not just be dropped. Runtime results are marked with the non-constant sentinel "@" (which IsImm/ConstInt reject); constant folding (NegFold/Fold2/StrFold) and every check stay.

  • 6001af6 SimExpr, 5fb26e7 Term, 4f2afa0 Expr, d469c78 Fact runtime builtins/calls.
  • 90d2529 the coupled change: Design/Fact/ArgList/ActParam together, dropping the NoteAddr/AddrOfVal note chain from the grammar (kept for Lower) and replacing VAR-actual addressability with an AST-kind check (NkDesignator, or NkStrLit).
  • 0667290 Design/ResultComp, 0ebe92e assignment, f59b784 remaining runtime value ops, 02f2526 ProcHeading's output.

6. -lower retired (bcb93a5)

With one emitter, -lower no longer changed what was emitted — only the image name for a byte-compare that compared Lower against itself. IsLowerParam/lowerTest/EndSession's testSuffix and the 23 lower_ok/lower_ok_files cases are gone; the covered programs are still exercised by the expect_run/session cases. Suite 204 → 181.

Traps worth remembering

  • ArgList alone can't be decoupled: dropping its CallBegin (fewer NewTemps) shifts the note-ring keys and breaks calls — 75 suite failures. Design→Fact→ArgList→ActParam is one unit.
  • An empty (. .) action in ProcDecl generated a bare ; empty statement that gm2 accepts but the V3 self-host grammar rejects — the suite passed while the fixpoint failed.

What remains

The grammar's remaining QbeGen calls are not removable emit:

  • the value layer (CopyOp, IsImm, IntStr, Fold2, Norm*, Conv*, CharVal, StrFold/StrEq, CapQ/AbsQ/NotQ, UStrLen) — computes constants and the q Lower.NoteConstVal consumes; and
  • the parser-state / lifecycle (SetModule, BeginFunc/ FuncParam/EndFunc/AbortFunc, PushWith/PopWith/PushLoop/ PopLoop, TopWith/TopLoop/LocFull, DeclStr/DeclUStr, SetLit/Ctor*).

Deleting SaveSession/RestoreSession/SetNoEmit (the last item) requires the grammar to stop writing entirely, which means reimplementing the parameter-scope bookkeeping (FuncParam needs BeginFunc's nPar reset) and the WITH/EXIT stacks outside QbeGen — a reimplementation, not cleanup.

Fixpoint sizes across the work

step bytes
flip 3,553,701
statements + declarations + bug fix 3,538,557
dead-code sweep 3,420,733
decoupling (expr layer) 3,406,951
decoupling complete (Design/Fact/calls/assignment/ProcHeading) 3,377,935
-lower retired + orphan cleanup 3,367,634

~186 KB smaller than at the flip, all green.