# 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 `"_"` 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' `"_"` 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). ## Plan (revised) The session re-emit is a useful *verification* tool for small sessions, but the flip needs per-unit emission. Concretely: 1. Fix the external-proc flag (`NoteProc` should record `IsExternal` at the EXTERNAL arm, not at the heading). 2. Decide the flip mechanism: **two `QbeGen` states**, or remove the grammar's inline `QbeGen` calls. Either is a big, non-incremental change; the fixpoint is the gate. 3. Then `Lower` runs per unit (AST valid, scope open), which also makes the external-proc and module-level resolution work naturally.