# Lower phase — slice 3 (procedures): groundwork + a blocking finding Branch `ast-stage-c`. Suite **182/182**; fixpoint **OK** (**3,147,849 bytes**; L2 was 3,123,487). L3 is **not** enabled — see the finding; it stays out of `CanLower`, so the comparisons cannot false-fail. ## What landed (groundwork, inert) - **Call support** in `Lower`: `NkCall` in expressions (value) and statements (discard), mirroring `ArgList`/`ActParam` — `CallBegin`, per-actual `CallArg` (VAR via `AddrOfVal`, value via `ArgClass` + real widening), `CallEnd`. `CanCallArgs` checks arity. - **`SymTab.ParamName`** (new, read-only) — the i-th formal's name, for the header. - **Procedure headers** in `LowerDecls`: `Mangled`/`BeginFunc`/`FuncParam` for each formal/`SetFuncRes`/`EndFuncHeader`/`EndFunc`. - **`Block` now records its local `NkDeclSeq`** (`astBlkDecls`), stored as `NkProcDecl` child 2 — so a proc's `VAR` locals are in the AST. - **Callee extraction fix**: `NkCall` child 0 is an `NkDesignator`, not a bare `NkIdent`; `CalleeName` unwraps it (this fixed calls mangling as `$_0`). ## The blocking finding — scopes close before `Lower` runs The L3 comparison does **not** match, and the reason is structural, not a missing emit: `SymTab.CloseProc` (and `EndUnit`) **close the scope** that the inline actions lowered *inside of*. `Lower` runs at the end (module end), by which time a procedure's locals/params are no longer resolvable — so `LowerDesign("s")` fails and the body emits nothing. - Module-level names survive until `EndUnit`, so L0–L2 (program body) work. - Procedure bodies do **not**: they must be lowered while the procedure scope is still open. This is exactly the "flip" the plan anticipated (§3 phase 3): the walk cannot be one end-of-unit pass for *everything*; body lowering has to happen at each procedure while `SymTab` still has its scope. Options: 1. **Grammar calls `Lower.LowerProc(node)` right before `SymTab.CloseProc`** (and the program body before `EndUnit`) — i.e. per-unit-piece lowering, still writing the `L` image. 2. **Make `SymTab` keep enough per-symbol info** (kind/type/param names) accessible after the scope closes (a durable "codegen view"), so one end-of-unit walk works. Option 1 matches the existing inline timing and is the lower-risk route; it also foreshadows the flip, where `Lower` *replaces* the inline emit at that same point. ## Verified this slice - L0–L2 comparisons still pass (`t_exit`, `t_arith`, `t_lower1`, `t_lower2`); `t_lower3` is **gated out** of `CanLower`. - The call-mangling and callee-designator fixes are exercised by the parse (and would be by a future flip). ## Next Decide option 1 vs 2 for scope timing, then finish L3 (procedures). The rest of the subset order (L4+) is unchanged.