# V3 step 4.1 — procedure calls: emission, frames, recursion (done 2026-09-22) Suite 79/79 (37 run — incl. 4 trap-aborts — + 42 reject). LL(1)-clean (only the known-benign TypeBlock advisory). Module-level procedures only; nested lowering = 4.2. ## What lowers now - **Calls as expressions and statements**: value params (with `INTEGER`→`REAL` conversion), `VAR` params by address, function results, recursion, mutual recursion through `FORWARD`, open-array `VAR` formals over fixed actuals, string literals into open `CHAR` formals. Arity/type failures are 233 (`invalid call`, new message); proper-as- function and function-as-statement are 233; composite results stay 230; open *value* formals are 230. - **Frames**: value params copied to slots on entry (scalars, pointers, sets, arrays via `CopyArray`, records via `CopyRecord`); `VAR` params stay addresses; locals in stack slots (`alloc4/8`); named consts get function-mangled data. Same-named methods mangle by proc uid (`$Init_3`). - **Bodies**: `RETURN` emits (`ret`, converted when widening); fall-off-end gets a typed default; `FORWARD` headings emit nothing (buffered header discarded). ## Backend lessons (all probed, not guessed) - QBE accepts statement-calls, 8-arg spill, `l` params, recursion, and forward references (all proven before use). - **Reentrancy**: nested calls (`Acc(t, Double(5))`) clobbered the shared call state (wrong target + merged args). Fixed with a 16-deep call stack; 64-arg/level cap → 233. - **Zero-init locals**: the frontend's dead target-load (Test2 heritage, harmless for globals) reads never-stored stack slots, which QBE rejects. Scalar/pointer/set slots now zero at alloc — consistent with the zero-initialized globals (arrays/records need no loads: `AddrOf` only). - Single-letter SSA temps mislink (3.1 rule, re-observed). ## SymTab (additive) `ProcUid` (mangled names), `ProcNPar`/`ParamType`/`ParamIsVar` (call checking), `ProcDepth` (nesting detection), `VarParamOk` (same type, fixed→open, literal→open-CHAR). `NIL` (`FNil`, 3.6) needed zero changes — `p := NIL`, `p = NIL` and the 233s all worked through the new paths untouched. ## Grammar notes - `NIL` exempted from the named-const 230 (else nothing with `NIL` compiles); bare proc names shed their eager 230 (the following `ArgList` decides; `Fact` reports 230 when none). - `AssOrCall` merges assignment and statement calls (`:=` vs `(` are LL(1)-distinct); bare `P;` is now a syntax error (was silently accepted — strictly better). - VAR actuals resolve through a temp→address note table (ring of 256; exact because SSA temps never repeat), so no expression-rule signatures changed. WITH-field actuals work (un-deferring the step-3 note). - `F()^` result suffixes are still syntax errors (deferred). ## Tests `t_call` → 125 (results, recursion), `t_varpar` → 40 (swap, address semantics), `t_mutual` → 11, `t_withfield` → 13, `t_valarray` → 6 (copy proven: aliasing would give 104), `t_openarr` → 7, `t_nestedproc` → 230; `t_proc`/`t_forward` promoted to run (0); `t_bad_arity`/`t_bad_actual`/ `t_bad_funstmt` → 233; `showcase9` → 140. Total 79. Deferred: nested/display (4.2), separate compilation + opaque completion + `HIGH`/`LEN` (4.3), proc-type vars, `F()^` suffixes, >64 params, recursive `DISPOSE`.