# 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 `NewTemp`s) 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.