summary_two-phase-lower3-finding.md 2.8 KB

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.