Преглед изворни кода

v3 step 4.1 — procedure calls: emission, frames, recursion (79/79 tests green)

Eric Streit пре 2 недеља
родитељ
комит
a23793bafe

+ 13 - 2
compiler/run_tests.sh

@@ -76,14 +76,22 @@ expect_fail showcase5.mod "not supported yet"
 expect_run showcase6.mod 34
 expect_run showcase7.mod 219
 expect_run showcase8.mod 170
+expect_run showcase9.mod 140
 expect_run t_pointer.mod 42
 expect_run t_ptrarray.mod 15
 expect_run t_ptrrec.mod 15
 expect_run t_ptrnest.mod 10
 expect_fail t_opaqueptr.mod "not supported yet"
 expect_fail t_enumdecl.mod "not supported yet"
-expect_fail t_proc.mod "not supported yet"
-expect_fail t_forward.mod "not supported yet"
+expect_run t_proc.mod 0
+expect_run t_forward.mod 0
+expect_run t_call.mod 125
+expect_run t_varpar.mod 40
+expect_run t_mutual.mod 11
+expect_run t_withfield.mod 13
+expect_run t_valarray.mod 6
+expect_run t_openarr.mod 7
+expect_fail t_nestedproc.mod "not supported yet"
 expect_fail t_bad_undecl.mod "undeclared identifier"
 expect_fail t_bad_dup.mod "duplicate identifier"
 expect_fail t_bad_mismatch.mod "module name mismatch"
@@ -93,6 +101,9 @@ expect_fail t_bad_exit.mod "not supported yet"
 expect_fail t_bad_return.mod "bad RETURN"
 expect_fail t_bad_for.mod "FOR needs integer variable and bounds"
 expect_fail t_bad_mixed.mod "incompatible assignment"
+expect_fail t_bad_arity.mod "invalid call"
+expect_fail t_bad_actual.mod "invalid call"
+expect_fail t_bad_funstmt.mod "invalid call"
 expect_fail t_bad_deref.mod "not a POINTER type"
 expect_fail t_bad_new.mod "not a POINTER type"
 expect_fail t_bad_field.mod "unknown field"

+ 228 - 34
compiler/src/M2.atg

@@ -97,7 +97,8 @@ PRODUCTIONS
       ";"
       { Import }
       { ConstBlock | TypeBlock<TRUE> | VarBlock
-      | ProcHeading<pn> ";"             (. SymTab.CloseProc; .) }
+      | ProcHeading<pn> ";"             (. SymTab.CloseProc;
+                                             QbeGen.AbortFunc; .) }
       "END"
       GetIdent<m2>                      (. IF ~SymTab.Equal(m1, m2) THEN
                                              SemError(202) END; .)
@@ -371,7 +372,8 @@ PRODUCTIONS
       [ Parents<ct> ]
       ";"
       { ClassField<ct> ";" }
-      { MethodHeading<pn> ";"           (. SymTab.CloseProc; .) }
+      { MethodHeading<pn> ";"           (. SymTab.CloseProc;
+                                             QbeGen.AbortFunc; .) }
       "END"
       GetIdent<m2>                      (. IF ~SymTab.Equal(cn, m2) THEN
                                              SemError(202) END;
@@ -441,7 +443,8 @@ PRODUCTIONS
                                            SymTab.PopScope;
                                            SemError(230); .) .
   MethodImpl<ct: SymTab.TypeIndex>      (. VAR pn: SymTab.Name; .)
-    = MethodHeading<pn> ";"
+    =                                   (. QbeGen.SetNoEmit(TRUE); .)
+      MethodHeading<pn> ";"
                                         (. IF (ct #
                                               SymTab.InvalidType)
                                              & ~SymTab.MethodExists(ct,
@@ -449,8 +452,10 @@ PRODUCTIONS
                                              SemError(201) END; .)
       ( "FORWARD"                       (. SymTab.MarkFwd;
                                            SymTab.CloseProc; .)
-      | Block<pn>                       (. SymTab.CloseProc;
-                                           SemError(230); .) ) .
+      | Block<pn>                       (. SymTab.CloseProc; .) )
+                                        (. QbeGen.AbortFunc;
+                                           QbeGen.SetNoEmit(FALSE);
+                                           SemError(230); .) .
   ConstBlock
     = "CONST" { ConstDecl ";" } .
   ConstDecl                             (. VAR n: SymTab.Name;
@@ -506,36 +511,79 @@ PRODUCTIONS
                                              SemError(200) END; .)
       { "," GetIdent<n>                 (. IF ~SymTab.EnterParam(n, isV) THEN
                                              SemError(200) END; .) } .
-  (* Procedure headings enter now (scopes, params, result); bodies
-     parse + check with one 230 at END (lowering = step 4).
+  (* Procedure headings enter scopes/params/result and buffer the
+     QBE header; bodies lower to functions (4.1, module level only).
      FORWARD marks; the body heading re-enters (signature compare
-     tightens in step 4). *)
-  ProcHeading<VAR pn: SymTab.Name>      (. VAR t: SymTab.TypeIndex; .)
+     deferred). Nested procedures parse + check, lowering = 4.2. *)
+  ProcHeading<VAR pn: SymTab.Name>      (. VAR t: SymTab.TypeIndex;
+                                             mg: QbeGen.QVal; .)
     = "PROCEDURE"
       GetIdent<pn>                      (. IF ~SymTab.EnterProc(pn) THEN
                                            IF ~SymTab.ReenterProc(pn) THEN
                                              SemError(200) END
-                                           END; .)
+                                           END;
+                                           QbeGen.Mangled(pn,
+                                             SymTab.ProcUid(pn), mg);
+                                           QbeGen.BeginFunc(mg); .)
       [ FormalParams ]
       [ ":" TypeIdent<t>                (. SymTab.SetProcRes(t);
+                                           QbeGen.SetFuncRes(t);
                                            IF (t #
                                               SymTab.InvalidType)
-                                              & (SymTab.ClassOf(t)
-                                                 = SymTab.ClArray) THEN
+                                              & ((SymTab.ClassOf(t)
+                                                 = SymTab.ClArray)
+                                              OR (SymTab.ClassOf(t)
+                                                 = SymTab.ClRecord)
+                                              OR (SymTab.ClassOf(t)
+                                                 = SymTab.ClSet)
+                                              OR (SymTab.ClassOf(t)
+                                                 = SymTab.ClClass)) THEN
                                              SemError(230) END; .) ] .
   FormalParams
     = "(" [ ParamSection { ";" ParamSection } ] ")" .
   ParamSection                          (. VAR t: SymTab.TypeIndex;
+                                             nm: SymTab.Name;
+                                             i: CARDINAL;
                                              isV: BOOLEAN; .)
     =                                   (. isV := FALSE; .)
       [ "VAR"                           (. isV := TRUE; .) ]
-      ParIdents<isV> ":" Type<t, TRUE>  (. SymTab.FixPending(t); .) .
-  ProcDecl                              (. VAR pn, m2: SymTab.Name; .)
-    = ProcHeading<pn> ";"
+      ParIdents<isV> ":" Type<t, TRUE>  (. i := 0;
+                                           WHILE i < SymTab.PendCount() DO
+                                             SymTab.PendName(i, nm);
+                                             IF ~QbeGen.FuncParam(nm,
+                                                  isV, t) THEN
+                                               SemError(233) END;
+                                             INC(i)
+                                           END;
+                                           SymTab.FixPending(t);
+                                           IF ~isV
+                                              & SymTab.IsOpenArray(t) THEN
+                                             SemError(230) END; .) .
+  ProcDecl                              (. VAR pn: SymTab.Name;
+                                             nested: BOOLEAN; .)
+    =                                   (. nested :=
+                                             SymTab.ProcDepth() > 0;
+                                           IF nested THEN
+                                             QbeGen.SetNoEmit(TRUE)
+                                           END; .)
+      ProcHeading<pn> ";"
       ( "FORWARD"                       (. SymTab.MarkFwd;
-                                           SymTab.CloseProc; .)
-      | Block<pn>                       (. SymTab.CloseProc;
-                                           SemError(230); .) ) .
+                                           SymTab.CloseProc;
+                                           QbeGen.AbortFunc;
+                                           IF nested THEN
+                                             QbeGen.SetNoEmit(FALSE);
+                                             SemError(230)
+                                           END; .)
+      |                                 (. QbeGen.EndFuncHeader; .)
+        Block<pn>                       (. SymTab.CloseProc;
+                                           IF nested THEN
+                                             QbeGen.AbortFunc;
+                                             QbeGen.SetNoEmit(FALSE);
+                                             SemError(230)
+                                           ELSE
+                                             QbeGen.EndFunc(
+                                               SymTab.ProcRes(pn))
+                                           END; .) ) .
   Block<pn: SymTab.Name>                (. VAR m2: SymTab.Name; .)
     = DeclSeq
       [ "BEGIN"
@@ -546,7 +594,7 @@ PRODUCTIONS
   StatSeq
     = Statement { ";" Statement } .
   Statement                             (. VAR lx: QbeGen.QVal; .)
-    = Assign
+    = AssOrCall
     | IfStat
     | WhileStat
     | RepeatStat
@@ -652,14 +700,19 @@ PRODUCTIONS
                                              QbeGen.PushWith(qd);
                                              INC(nW)
                                            END; .) .
-  Assign                                (. VAR dt, et: SymTab.TypeIndex;
+  (* Assignment or procedure-statement call (4.1, module level).
+     Bare `P;` is a syntax error; function-as-statement is 233. *)
+  AssOrCall                             (. VAR dt, et: SymTab.TypeIndex;
                                              dk: INTEGER;
                                              qd, qe, qt: QbeGen.QVal;
                                              qn: SymTab.Name;
+                                             ct2: SymTab.TypeIndex;
+                                             q2: QbeGen.QVal;
                                              isR, conv: BOOLEAN;
-                                             sfx: BOOLEAN; .)
-    = Design<dt, dk, qd, qn, sfx> ":="
-      Expr<et, qe>                      (. IF (dt # SymTab.InvalidType)
+                                             called, sfx: BOOLEAN; .)
+    = Design<dt, dk, qd, qn, sfx>
+      ( ":="
+        Expr<et, qe>                    (. IF (dt # SymTab.InvalidType)
                                            & (dk # SymTab.KindVar)
                                            & (dk # SymTab.KindParam)
                                            & (dk # SymTab.KindField) THEN
@@ -726,7 +779,99 @@ PRODUCTIONS
                                            ELSE
                                              QbeGen.StoreVar(qn, qe, isR)
                                            END
-                                         END; .) .
+                                         END; .)
+      | ArgList<qn, FALSE, ct2, q2, called> ) .
+  (* Actual-parameter list shared by statement and expression calls.
+     want selects CallEnd's result handling; t/q carry the call
+     value (statement calls discard). Arity/type failures are 233;
+     evaluation code still emits so the .ssa stays assembleable. *)
+  ArgList<pn: SymTab.Name; want: BOOLEAN;
+          VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal;
+          VAR called: BOOLEAN>          (. VAR i: CARDINAL;
+                                             res: SymTab.TypeIndex;
+                                             mg: QbeGen.QVal;
+                                             ok: BOOLEAN; .)
+    = "("                               (. called := TRUE;
+                                           res := SymTab.ProcRes(pn);
+                                           ok := TRUE;
+                                           IF SymTab.SymKind(pn) #
+                                              SymTab.KindProc THEN
+                                             SemError(233); ok := FALSE
+                                           ELSE QbeGen.Mangled(pn,
+                                             SymTab.ProcUid(pn), mg);
+                                             QbeGen.CallBegin(mg, res)
+                                           END;
+                                           i := 0; .)
+      [ ActParam<pn, i>                  (. INC(i); .)
+        { "," ActParam<pn, i>            (. INC(i); .) } ]
+      ")"                               (. IF ok THEN
+                                             IF i #
+                                                SymTab.ProcNPar(pn) THEN
+                                               SemError(233); ok := FALSE
+                                             END
+                                           END;
+                                           IF ~ok THEN
+                                             t := SymTab.InvalidType;
+                                             QbeGen.CopyOp("0", q)
+                                           ELSIF want THEN
+                                             IF res =
+                                                SymTab.InvalidType THEN
+                                               SemError(233);
+                                               t := SymTab.InvalidType;
+                                               QbeGen.CopyOp("0", q)
+                                             ELSE t := res;
+                                               QbeGen.CallEnd(TRUE, q)
+                                             END
+                                           ELSE
+                                             IF res #
+                                                SymTab.InvalidType THEN
+                                               SemError(233)
+                                             END;
+                                             t := SymTab.InvalidType;
+                                             QbeGen.CopyOp("0", q);
+                                             QbeGen.CallEnd(FALSE, q)
+                                           END; .) .
+  (* One actual: VAR formals take recorded designator addresses
+     (233 otherwise); value formals take converted expressions. *)
+  ActParam<pn: SymTab.Name; i: CARDINAL>(. VAR at, ft: SymTab.TypeIndex;
+                                             qe, qa, qt: QbeGen.QVal;
+                                             isV, conv: BOOLEAN; .)
+    = Expr<at, qe>                      (. ft := SymTab.ParamType(pn, i);
+                                           isV := SymTab.ParamIsVar(pn, i);
+                                           IF (at = SymTab.InvalidType)
+                                              OR (ft =
+                                                 SymTab.InvalidType) THEN
+                                           ELSIF isV THEN
+                                             IF ~QbeGen.AddrOfVal(qe,
+                                                  qa) THEN
+                                               SemError(233)
+                                             ELSIF ~SymTab.VarParamOk(at,
+                                                      ft) THEN
+                                               SemError(233)
+                                             ELSIF ~QbeGen.CallArg(qa,
+                                                      "l") THEN
+                                               SemError(233)
+                                             END
+                                           ELSE
+                                             IF ~SymTab.Assignable(at,
+                                                  ft) THEN
+                                               SemError(233)
+                                             ELSE
+                                               conv := (SymTab.ClassOf(
+                                                 ft) = SymTab.ClReal)
+                                                 & SymTab.IsIntFamily(at);
+                                               IF conv THEN
+                                                 QbeGen.ConvIR(qe, qt);
+                                                 IF ~QbeGen.CallArg(qt,
+                                                    "d") THEN
+                                                   SemError(233)
+                                                 END
+                                               ELSIF ~QbeGen.CallArg(qe,
+                                                 QbeGen.ArgClass(ft)) THEN
+                                                 SemError(233)
+                                               END
+                                             END
+                                           END; .) .
   IfStat                                (. VAR t: SymTab.TypeIndex;
                                              q, lThen, lElse, lEnd:
                                                QbeGen.QVal;
@@ -931,18 +1076,22 @@ PRODUCTIONS
                                            QbeGen.Jnz(qe, lBody, lNext);
                                            QbeGen.EmitLabel(lNext); .) ] .
   ReturnStat                            (. VAR t: SymTab.TypeIndex;
-                                             q: QbeGen.QVal;
+                                             q, qt: QbeGen.QVal;
                                              res: SymTab.TypeIndex;
-                                             hadE: BOOLEAN; .)
+                                             hadE, conv: BOOLEAN; .)
     = "RETURN"                          (. hadE := FALSE; .)
       [ Expr<t, q>                      (. hadE := TRUE; .) ]
-                                        (. IF ~SymTab.InProc() THEN
+                                        (. conv := FALSE;
+                                           IF ~SymTab.InProc() THEN
                                              SemError(232)
                                            ELSE res := SymTab.CurRes();
                                              IF ~hadE THEN
                                                IF res #
                                                   SymTab.InvalidType THEN
-                                                 SemError(232) END
+                                                 SemError(232)
+                                               ELSE QbeGen.EmitRet(q,
+                                                 FALSE)
+                                               END
                                              ELSIF (res =
                                                     SymTab.InvalidType)
                                                 OR (t #
@@ -950,6 +1099,15 @@ PRODUCTIONS
                                                 & ~SymTab.Assignable(t,
                                                      res) THEN
                                                SemError(232)
+                                             ELSE
+                                               conv := (SymTab.ClassOf(
+                                                 res) = SymTab.ClReal)
+                                                 & SymTab.IsIntFamily(t);
+                                               IF conv THEN
+                                                 QbeGen.ConvIR(q, qt);
+                                                 QbeGen.EmitRet(qt, TRUE)
+                                               ELSE QbeGen.EmitRet(q, TRUE)
+                                               END
                                              END
                                            END; .) .
   (* Designator: scalar loads, array addresses, and index suffixes.
@@ -1044,9 +1202,15 @@ PRODUCTIONS
                                                  QbeGen.CopyOp("0", q)
                                                END
                                              ELSE QbeGen.CopyOp("0", q);
-                                               IF (k = SymTab.KindImport)
-                                                  OR (k = SymTab.KindProc) THEN
+                                               IF k = SymTab.KindImport THEN
                                                  SemError(230)
+                                               ELSIF k =
+                                                  SymTab.KindProc THEN
+                                                 (* bare procedure name:
+                                                    a following ArgList
+                                                    makes it a call;
+                                                    otherwise Fact
+                                                    reports 230 *)
                                                ELSE
                                                  IF k = SymTab.KindField THEN
                                                    IF QbeGen.TopWith(qb) THEN
@@ -1370,11 +1534,13 @@ PRODUCTIONS
     | "AND"                             (. op := SymTab.OpAnd; .) .
   Fact<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
                                         (. VAR s: ARRAY [0 .. 255] OF CHAR;
-                                             et, dt, t2, st: SymTab.TypeIndex;
+                                             et, dt, t2, st, ct2:
+                                               SymTab.TypeIndex;
                                              dk: INTEGER;
-                                             qd, q2, sq: QbeGen.QVal;
+                                             qd, q2, sq, qa:
+                                               QbeGen.QVal;
                                              qn: SymTab.Name;
-                                             sfx: BOOLEAN; .)
+                                             called, sfx: BOOLEAN; .)
     = integer                           (. LexString(s);
                                            QbeGen.NormInt(s, q);
                                            t := SymTab.IntType(); .)
@@ -1389,7 +1555,8 @@ PRODUCTIONS
                                            ELSE t := SymTab.NewStr();
                                              QbeGen.DeclStr(s, q)
                                            END; .)
-    | Design<dt, dk, qd, qn, sfx>       (. t := dt;
+    | Design<dt, dk, qd, qn, sfx>       (. called := FALSE;
+                                           t := dt;
                                            IF sfx THEN
                                              IF dt =
                                                 SymTab.InvalidType THEN
@@ -1405,7 +1572,34 @@ PRODUCTIONS
                                                q)
                                              END
                                            ELSE QbeGen.CopyOp(qd, q)
+                                           END;
+                                           IF (dk = SymTab.KindVar)
+                                              OR (dk = SymTab.KindParam)
+                                              OR (dk =
+                                                 SymTab.KindField) THEN
+                                             IF sfx THEN
+                                               QbeGen.NoteAddr(q, qd)
+                                             ELSE
+                                               QbeGen.AddrOf(qn, qa);
+                                               QbeGen.NoteAddr(q, qa)
+                                             END
+                                           ELSIF sfx
+                                              & (dt #
+                                                 SymTab.InvalidType)
+                                              & ((SymTab.ClassOf(dt) =
+                                                  SymTab.ClArray)
+                                                 OR (SymTab.ClassOf(dt) =
+                                                     SymTab.ClSet)
+                                                 OR (SymTab.ClassOf(dt) =
+                                                     SymTab.ClRecord)) THEN
+                                             QbeGen.NoteAddr(qd, qd)
                                            END; .)
+      [ ArgList<qn, TRUE, ct2, q2, called>
+                                        (. t := ct2;
+                                           QbeGen.CopyOp(q2, q); .) ]
+                                        (. IF ~called
+                                             & (dk = SymTab.KindProc) THEN
+                                             SemError(230) END; .)
     | "(" Expr<et, q> ")"               (. t := et; .)
     | SetLit<st, sq>                    (. t := st;
                                            QbeGen.CopyOp(sq, q); .)

Разлика између датотеке није приказан због своје велике величине
+ 1586 - 1392
compiler/src/M2.lst


+ 54 - 0
compiler/src/QbeGen.def

@@ -36,6 +36,53 @@ PROCEDURE CloseModule;
    lowering waits for step 4: definitions, implementations,
    and any unit containing nested modules). *)
 
+PROCEDURE SetNoEmit (b: BOOLEAN);
+(* Suppresses all output while TRUE (nested procedures in 4.1:
+   SymTab tracks, QbeGen stays silent). *)
+
+PROCEDURE Mangled (pname: ARRAY OF CHAR; uid: CARDINAL; VAR q: QVal);
+(* q := "name_uid" (function symbols; same-named methods never
+   collide; deterministic). *)
+
+PROCEDURE BeginFunc (mangled: ARRAY OF CHAR);
+(* Opens function context (locals cleared, header buffering). *)
+
+PROCEDURE SetFuncRes (t: INTEGER);
+(* Sets the buffered header's result class. *)
+
+PROCEDURE FuncParam (name: ARRAY OF CHAR; isVar: BOOLEAN;
+                     t: INTEGER): BOOLEAN;
+(* Records one formal (FALSE past 64 → 233). Header class 'l'
+   for all composites; entry copies restore value semantics. *)
+
+PROCEDURE EndFuncHeader;
+(* Emits the header + body label + entry copies (params to slots). *)
+
+PROCEDURE AbortFunc;
+(* Discards function context without emitting (DefUnit headings,
+   FORWARD, methods, nested-in-4.1). *)
+
+PROCEDURE EndFunc (resT: INTEGER);
+(* Default return by result class, close, leave function context. *)
+
+PROCEDURE EmitRet (q: ARRAY OF CHAR; hasVal: BOOLEAN);
+(* Emits "ret q" (or dummy "ret 0"); always a terminator. *)
+
+PROCEDURE CallBegin (mangled: ARRAY OF CHAR; resT: INTEGER);
+(* Starts accumulating a call's actuals. *)
+
+PROCEDURE CallArg (q: ARRAY OF CHAR; cls: CHAR): BOOLEAN;
+(* Appends one typed actual (FALSE when full → 233). *)
+
+PROCEDURE CallEnd (wantRes: BOOLEAN; VAR q: QVal);
+(* Emits the call (result temp in q when wanted). *)
+
+PROCEDURE InitStack (addr: ARRAY OF CHAR; t: INTEGER);
+(* Like InitHeap but nested objects go to stack slots. *)
+
+PROCEDURE ArgClass (t: INTEGER): CHAR;
+(* Formal/actual class for call emission. *)
+
 PROCEDURE NewTemp (VAR t: QVal);
 (* Fresh "%tN" operand (deterministic counter: fixpoint-safe). *)
 
@@ -97,6 +144,13 @@ PROCEDURE ConvIR (a: ARRAY OF CHAR; VAR q: QVal);
 (* q := fresh double holding "swtof a" (INTEGER -> REAL widening). *)
 
 PROCEDURE CopyOp (s: ARRAY OF CHAR; VAR d: ARRAY OF CHAR);
+
+PROCEDURE NoteAddr (val: ARRAY OF CHAR; addr: ARRAY OF CHAR);
+(* Records a value-temp → address mapping for VAR actuals. *)
+
+PROCEDURE AddrOfVal (val: ARRAY OF CHAR; VAR addr: QVal): BOOLEAN;
+(* Retrieves the recorded address (FALSE → actual is not an
+   addressable designator → 233). *)
 PROCEDURE AddrOf (name: ARRAY OF CHAR; VAR q: QVal);
 (* q := "$name" descriptor-address operand. *)
 PROCEDURE IntStr (v: INTEGER; VAR s: QVal);

+ 476 - 22
compiler/src/QbeGen.mod

@@ -22,6 +22,35 @@ VAR
   strTexts : ARRAY [0 .. 255] OF ARRAY [0 .. 255] OF CHAR;
   withTop : CARDINAL;
   withSt  : ARRAY [0 .. 7] OF QVal;
+  noEmit : BOOLEAN;  (* TRUE while parsing nested procedures in 4.1:
+                        SymTab tracks, QbeGen suppresses output *)
+  inFunc : BOOLEAN;  (* TRUE inside an emitted function body *)
+  useStack : BOOLEAN;(* TRUE: NewHeap allocates stack slots *)
+  funcRes : CHAR;    (* current function result class *)
+  nLoc : CARDINAL;   (* function-local name table entries *)
+  locNames : ARRAY [0 .. 63] OF SymTab.Name;
+  locTag : ARRAY [0 .. 63] OF INTEGER;  (* 0 slot, 1 data, 2 addr *)
+  locRep : ARRAY [0 .. 63] OF QVal;     (* slot temp / mangled / addr *)
+  locCls : ARRAY [0 .. 63] OF CHAR;
+  locTyp : ARRAY [0 .. 63] OF INTEGER;
+  nPar : CARDINAL;   (* recorded formal params for the header *)
+  parCls : ARRAY [0 .. 63] OF CHAR;
+  parTmp : ARRAY [0 .. 63] OF QVal;
+  parNam : ARRAY [0 .. 63] OF SymTab.Name;
+  parVar : ARRAY [0 .. 63] OF BOOLEAN;
+  parTyp : ARRAY [0 .. 63] OF INTEGER;
+  argBuf : ARRAY [0 .. 4095] OF CHAR;   (* accumulated call args *)
+  hbuf : ARRAY [0 .. 2047] OF CHAR;     (* buffered function header *)
+  nArg : CARDINAL;
+  callDepth : CARDINAL;                 (* nested-call stack *)
+  stkName : ARRAY [0 .. 15] OF QVal;
+  stkRes : ARRAY [0 .. 15] OF CHAR;
+  stkArg : ARRAY [0 .. 15] OF ARRAY [0 .. 1023] OF CHAR;
+  stkN : ARRAY [0 .. 15] OF CARDINAL;
+  funcName : QVal;   (* current function / call target *)
+  nVal : ARRAY [0 .. 255] OF QVal;  (* VAR-actual note keys *)
+  nAddr : ARRAY [0 .. 255] OF QVal; (* VAR-actual note addresses *)
+  nn : CARDINAL;
 
 (* ---------------- small string utilities ---------------- *)
 
@@ -57,15 +86,26 @@ PROCEDURE App (VAR d: ARRAY OF CHAR; s: ARRAY OF CHAR);
 
 PROCEDURE W (s: ARRAY OF CHAR);
   BEGIN
-    IF opened THEN FileIO.WriteString(out, s) END
+    IF opened & ~noEmit THEN FileIO.WriteString(out, s) END
   END W;
 
 PROCEDURE WL (s: ARRAY OF CHAR);
   BEGIN
     W(s);
-    IF opened THEN FileIO.WriteLn(out) END
+    IF opened & ~noEmit THEN FileIO.WriteLn(out) END
   END WL;
 
+PROCEDURE SetNoEmit (b: BOOLEAN);
+  BEGIN
+    noEmit := b
+  END SetNoEmit;
+
+PROCEDURE Wc (c: CHAR);
+(* Writes a single class character (w/d/l). *)
+  BEGIN
+    IF opened & ~noEmit THEN FileIO.Write(out, c) END
+  END Wc;
+
 PROCEDURE AppNum (VAR d: ARRAY OF CHAR; v: CARDINAL);
 (* Appends v in decimal to d. *)
   VAR buf : ARRAY [0 .. 15] OF CHAR;
@@ -91,10 +131,46 @@ PROCEDURE CopyOp (s: ARRAY OF CHAR; VAR d: ARRAY OF CHAR);
     Cpy(d, s)
   END CopyOp;
 
+PROCEDURE NoteAddr (val: ARRAY OF CHAR; addr: ARRAY OF CHAR);
+(* Records value-temp → address mapping for VAR actuals. Ring of
+   256: safe because SSA temps are never reused (stale entries can
+   never false-match, only age out on pathological expressions). *)
+  BEGIN
+    Cpy(nVal[nn], val);
+    Cpy(nAddr[nn], addr);
+    nn := nn + 1;
+    IF nn > HIGH(nVal) THEN nn := 0 END
+  END NoteAddr;
+
+PROCEDURE AddrOfVal (val: ARRAY OF CHAR; VAR addr: QVal): BOOLEAN;
+  VAR i : CARDINAL;
+  BEGIN
+    i := 0;
+    WHILE i <= HIGH(nVal) DO
+      IF SymTab.Equal(nVal[i], val) THEN
+        Cpy(addr, nAddr[i]);
+        RETURN TRUE
+      END;
+      INC(i)
+    END;
+    RETURN FALSE
+  END AddrOfVal;
+
 PROCEDURE AddrOf (name: ARRAY OF CHAR; VAR q: QVal);
+  VAR idx : INTEGER;
   BEGIN
-    Cpy(q, "$");
-    App(q, name)
+    idx := LocFind(name);
+    IF idx = -1 THEN
+      Cpy(q, "$");
+      App(q, name);
+      RETURN
+    END;
+    IF locTag[idx] = 1 THEN
+      Cpy(q, "$");
+      App(q, locRep[idx])
+    ELSE
+      Cpy(q, locRep[idx])
+    END
   END AddrOf;
 
 PROCEDURE IntStr (v: INTEGER; VAR s: QVal);
@@ -246,6 +322,8 @@ PROCEDURE OpenModule (name: ARRAY OF CHAR);
     dead := FALSE;
     nTemp := 0; nLab := 0; loopTop := 0; nR := 0; nStr := 0;
     withTop := 0;
+    noEmit := FALSE; inFunc := FALSE; useStack := FALSE;
+    nLoc := 0; nPar := 0; nArg := 0; nn := 0; callDepth := 0;
     WL("# QBE IR generated by the V3 step-1 backend");
     WL("")
   END OpenModule;
@@ -269,8 +347,17 @@ PROCEDURE DataLineL (name: ARRAY OF CHAR; init: ARRAY OF CHAR);
     WL(" }")
   END DataLineL;
 
+PROCEDURE DeclLocal (name: ARRAY OF CHAR; t: INTEGER);
+(* Function-local variable: stack slot + static init, recorded. *)
+  VAR slot : QVal;
+  BEGIN
+    AllocLocal(t, slot);
+    LocAdd(name, 0, slot, ResClass(t), t)
+  END DeclLocal;
+
 PROCEDURE DeclVar (name: ARRAY OF CHAR; t: INTEGER);
   BEGIN
+    IF inFunc THEN DeclLocal(name, t); RETURN END;
     IF SymTab.ClassOf(t) = SymTab.ClReal THEN DataLine(name, TRUE, "0")
     ELSIF SymTab.ClassOf(t) = SymTab.ClArray THEN DeclArr(name, t)
     ELSIF SymTab.ClassOf(t) = SymTab.ClSet THEN DeclSet(name, t)
@@ -308,19 +395,26 @@ PROCEDURE IsZeroReal (val: ARRAY OF CHAR): BOOLEAN;
 
 PROCEDURE DeclConst (name: ARRAY OF CHAR; val: ARRAY OF CHAR; t: INTEGER);
   VAR cls : INTEGER;
+    mang : QVal;
   BEGIN
     cls := SymTab.ClassOf(t);
+    IF inFunc THEN
+      Cpy(mang, funcName); App(mang, "_"); App(mang, name);
+      LocAdd(name, 1, mang, ResClass(t), t)
+    ELSE
+      Cpy(mang, name)
+    END;
     IF (cls = SymTab.ClStr) OR ~IsImm(val) THEN
-      DataLine(name, FALSE, "0"); RETURN
+      DataLine(mang, FALSE, "0"); RETURN
     END;
     IF cls = SymTab.ClReal THEN
       (* This qbe accepts only integer/zero literals in "data":
          non-zero REALs flush at BeginBody. *)
-      DataLine(name, TRUE, "0");
+      DataLine(mang, TRUE, "0");
       IF ~IsZeroReal(val) & (nR <= HIGH(rNames)) THEN
-        Cpy(rNames[nR], name); Cpy(rVals[nR], val); INC(nR)
+        Cpy(rNames[nR], mang); Cpy(rVals[nR], val); INC(nR)
       END
-    ELSE DataLine(name, FALSE, val)
+    ELSE DataLine(mang, FALSE, val)
     END
   END DeclConst;
 
@@ -370,6 +464,300 @@ PROCEDURE EndModule;
     opened := FALSE
   END EndModule;
 
+(* ---------------- functions and calls (step 4.1) ---------------- *)
+(* Module-level procedures only (nested → NoEmit until 4.2).
+   Value params arrive as SSA values and are copied to slots;
+   composite value params arrive as addresses and are copied;
+   VAR params stay addresses. Locals live in stack slots. *)
+
+PROCEDURE LocFind (name: ARRAY OF CHAR): INTEGER;
+  VAR i : CARDINAL;
+  BEGIN
+    i := 0;
+    WHILE i < nLoc DO
+      IF SymTab.Equal(locNames[i], name) THEN RETURN VAL(INTEGER, i) END;
+      INC(i)
+    END;
+    RETURN -1
+  END LocFind;
+
+PROCEDURE LocAdd (name: ARRAY OF CHAR; tag: INTEGER; rep: ARRAY OF CHAR;
+                   cls: CHAR; typ: INTEGER);
+  BEGIN
+    IF noEmit THEN RETURN END;
+    IF nLoc > HIGH(locNames) THEN RETURN END;
+    Cpy(locNames[nLoc], name);
+    locTag[nLoc] := tag;
+    Cpy(locRep[nLoc], rep);
+    locCls[nLoc] := cls;
+    locTyp[nLoc] := typ;
+    INC(nLoc)
+  END LocAdd;
+
+PROCEDURE ResClass (t: INTEGER): CHAR;
+  VAR cls : INTEGER;
+  BEGIN
+    cls := SymTab.ClassOf(t);
+    IF cls = SymTab.ClReal THEN RETURN "d" END;
+    IF (cls = SymTab.ClArray) OR (cls = SymTab.ClSet)
+       OR (cls = SymTab.ClRecord) OR (cls = SymTab.ClClass)
+       OR (cls = SymTab.ClPtr) THEN
+      RETURN "l"
+    END;
+    RETURN "w"
+  END ResClass;
+
+PROCEDURE Mangled (pname: ARRAY OF CHAR; uid: CARDINAL; VAR q: QVal);
+  BEGIN
+    Cpy(q, pname);
+    App(q, "_");
+    AppNum(q, uid)
+  END Mangled;
+
+PROCEDURE AllocLocal (t: INTEGER; VAR slot: QVal);
+(* Stack slot sized for t's inline footprint; arrays/records get
+   full objects + static init. Scalar/pointer/set slots are
+   zero-initialized: QBE rejects reads of never-stored slots, and
+   the frontend emits a dead load for assignment targets (Test2
+   heritage, harmless for zero-initialized globals). Locals match
+   the globals' zero-init convention. *)
+  VAR cls : INTEGER;
+    nb : QVal;
+  BEGIN
+    cls := SymTab.ClassOf(t);
+    NewTemp(slot);
+    Revive;
+    W("  "); W(slot);
+    IF (cls = SymTab.ClArray) THEN
+      IntStr(VAL(INTEGER, HeapSize(t)), nb);
+      W(" =l alloc8 "); WL(nb);
+      InitStack(slot, t)
+    ELSIF (cls = SymTab.ClRecord) OR (cls = SymTab.ClClass) THEN
+      IntStr(VAL(INTEGER, SymTab.TypeSize(t)), nb);
+      W(" =l alloc8 "); WL(nb);
+      InitStack(slot, t)
+    ELSIF cls = SymTab.ClSet THEN
+      IntStr(VAL(INTEGER, SymTab.SetWords(t) * 4), nb);
+      W(" =l alloc8 "); WL(nb);
+      SetZero(slot, SymTab.SetWords(t))
+    ELSIF cls = SymTab.ClReal THEN
+      W(" =l alloc8 8"); WL("");
+      W("  stored d_0.0, "); WL(slot)
+    ELSIF cls = SymTab.ClPtr THEN
+      W(" =l alloc8 8"); WL("");
+      W("  storel 0, "); WL(slot)
+    ELSE
+      W(" =l alloc4 4"); WL("");
+      W("  storew 0, "); WL(slot)
+    END
+  END AllocLocal;
+
+PROCEDURE BeginFunc (mangled: ARRAY OF CHAR);
+(* Opens function context; the header is buffered until
+   EndFuncHeader (FORWARD/DefUnit paths discard via AbortFunc). *)
+  BEGIN
+    inFunc := TRUE;
+    nLoc := 0;
+    nPar := 0;
+    funcRes := "w";
+    Cpy(funcName, mangled);
+    hbuf[0] := 0C
+  END BeginFunc;
+
+PROCEDURE SetFuncRes (t: INTEGER);
+  BEGIN
+    funcRes := ResClass(t)
+  END SetFuncRes;
+
+PROCEDURE HApp (s: ARRAY OF CHAR);
+(* Appends to the header buffer (bounded: FormalParams cap it). *)
+  VAR i, j : CARDINAL;
+  BEGIN
+    i := 0;
+    WHILE (i <= HIGH(hbuf)) & (hbuf[i] # 0C) DO INC(i) END;
+    j := 0;
+    WHILE (i < HIGH(hbuf)) & (j <= HIGH(s)) & (s[j] # 0C) DO
+      hbuf[i] := s[j]; INC(i); INC(j)
+    END;
+    IF i <= HIGH(hbuf) THEN hbuf[i] := 0C END
+  END HApp;
+
+PROCEDURE FuncParam (name: ARRAY OF CHAR; isVar: BOOLEAN;
+                     t: INTEGER): BOOLEAN;
+  VAR c : CHAR;
+    tmp : QVal;
+    buf : ARRAY [0 .. 71] OF CHAR;
+  BEGIN
+    IF noEmit THEN RETURN TRUE END;
+    IF nPar > HIGH(parCls) THEN RETURN FALSE END;
+    IF isVar THEN c := "l" ELSE c := ResClass(t) END;
+    parCls[nPar] := c;
+    NewTemp(tmp);
+    Cpy(parTmp[nPar], tmp);
+    Cpy(parNam[nPar], name);
+    parVar[nPar] := isVar;
+    parTyp[nPar] := t;
+    IF nPar > 0 THEN HApp(", ") END;
+    buf[0] := c; buf[1] := " "; buf[2] := 0C;
+    App(buf, tmp);
+    HApp(buf);
+    INC(nPar);
+    RETURN TRUE
+  END FuncParam;
+
+PROCEDURE EndFuncHeader;
+(* Flushes the buffered header + body label + entry copies. *)
+  VAR i : CARDINAL;
+    slot, nb : QVal;
+    cls : INTEGER;
+  BEGIN
+    W("export function "); Wc(funcRes); W(" $"); W(funcName); W("(");
+    W(hbuf);
+    WL(") {");
+    WL("@start");
+    inBody := TRUE;
+    i := 0;
+    WHILE i < nPar DO
+      cls := SymTab.ClassOf(parTyp[i]);
+      IF parVar[i] THEN
+        LocAdd(parNam[i], 2, parTmp[i], parCls[i], parTyp[i])
+      ELSE
+        AllocLocal(parTyp[i], slot);
+        LocAdd(parNam[i], 0, slot, parCls[i], parTyp[i]);
+        IF cls = SymTab.ClArray THEN
+          CopyArray(slot, parTmp[i], parTyp[i])
+        ELSIF cls = SymTab.ClSet THEN
+          CopySet(slot, parTmp[i],
+            SymTab.SetWords(parTyp[i]), SymTab.SetWords(parTyp[i]))
+        ELSIF (cls = SymTab.ClRecord) OR (cls = SymTab.ClClass) THEN
+          CopyRecord(slot, parTmp[i], parTyp[i])
+        ELSIF cls = SymTab.ClReal THEN
+          Revive;
+          W("  stored "); W(parTmp[i]); W(", "); WL(slot)
+        ELSIF cls = SymTab.ClPtr THEN
+          Revive;
+          W("  storel "); W(parTmp[i]); W(", "); WL(slot)
+        ELSE
+          Revive;
+          W("  storew "); W(parTmp[i]); W(", "); WL(slot)
+        END
+      END;
+      INC(i)
+    END
+  END EndFuncHeader;
+
+PROCEDURE AbortFunc;
+(* Discards function context without emitting (DefUnit headings,
+   FORWARD, methods, nested-in-4.1): resets inFunc and params. *)
+  BEGIN
+    inFunc := FALSE;
+    inBody := FALSE;
+    nPar := 0;
+    nLoc := 0
+  END AbortFunc;
+
+PROCEDURE EndFunc (resT: INTEGER);
+  VAR rc : CHAR;
+  BEGIN
+    rc := ResClass(resT);
+    Revive;
+    IF rc = "d" THEN WL("  ret d_0.0")
+    ELSIF rc = "l" THEN WL("  ret 0")
+    ELSE WL("  ret 0")
+    END;
+    WL("}");
+    inFunc := FALSE;
+    inBody := FALSE
+  END EndFunc;
+
+PROCEDURE EmitRet (q: ARRAY OF CHAR; hasVal: BOOLEAN);
+  BEGIN
+    Revive;
+    IF hasVal THEN W("  ret "); WL(q)
+    ELSE WL("  ret 0")
+    END;
+    dead := TRUE
+  END EmitRet;
+
+PROCEDURE CallBegin (mangled: ARRAY OF CHAR; resT: INTEGER);
+  BEGIN
+    IF callDepth <= HIGH(stkName) THEN
+      Cpy(stkName[callDepth], mangled);
+      stkRes[callDepth] := ResClass(resT);
+      stkArg[callDepth][0] := 0C;
+      stkN[callDepth] := 0;
+      INC(callDepth)
+    END
+  END CallBegin;
+
+PROCEDURE CallArg (q: ARRAY OF CHAR; cls: CHAR): BOOLEAN;
+(* Appends "c operand, " to the current level. FALSE when full. *)
+  VAR need, have, i, j, d : CARDINAL;
+  BEGIN
+    d := callDepth - 1;
+    IF callDepth = 0 THEN RETURN FALSE END;
+    IF d > HIGH(stkArg) THEN d := HIGH(stkArg) END;
+    need := Len(q) + 5;
+    have := Len(stkArg[d]);
+    IF (stkN[d] >= 64) OR (have + need > HIGH(stkArg[d])) THEN
+      RETURN FALSE
+    END;
+    i := have; j := 0;
+    stkArg[d][i] := cls; INC(i);
+    stkArg[d][i] := " "; INC(i);
+    WHILE q[j] # 0C DO
+      stkArg[d][i] := q[j]; INC(i); INC(j)
+    END;
+    stkArg[d][i] := ","; INC(i);
+    stkArg[d][i] := " "; INC(i);
+    stkArg[d][i] := 0C;
+    INC(stkN[d]);
+    RETURN TRUE
+  END CallArg;
+
+PROCEDURE CallEnd (wantRes: BOOLEAN; VAR q: QVal);
+  VAR i, d : CARDINAL;
+    rc : CHAR;
+    body : ARRAY [0 .. 1023] OF CHAR;
+  BEGIN
+    IF callDepth = 0 THEN Cpy(q, "0"); RETURN END;
+    DEC(callDepth);
+    d := callDepth;
+    IF d > HIGH(stkArg) THEN d := HIGH(stkArg) END;
+    Cpy(funcName, stkName[d]);
+    rc := stkRes[d];
+    i := 0;
+    WHILE (stkArg[d][i] # 0C) & (i < HIGH(body)) DO
+      body[i] := stkArg[d][i]; INC(i)
+    END;
+    IF (i >= 2) & (body[i-2] = ",") THEN
+      body[i-2] := 0C
+    ELSE
+      body[i] := 0C
+    END;
+    Revive;
+    IF wantRes THEN
+      NewTemp(q);
+      W("  "); W(q); W(" ="); Wc(rc)
+    ELSE
+      W("  ")
+    END;
+    W(" call $"); W(funcName); W("("); W(body); WL(")")
+  END CallEnd;
+
+PROCEDURE InitStack (addr: ARRAY OF CHAR; t: INTEGER);
+  BEGIN
+    useStack := TRUE;
+    InitHeap(addr, t);
+    useStack := FALSE
+  END InitStack;
+
+PROCEDURE ArgClass (t: INTEGER): CHAR;
+(* Formal/actual class for call emission. *)
+  BEGIN
+    RETURN ResClass(t)
+  END ArgClass;
+
 (* ---------------- temporaries and operators ---------------- *)
 
 PROCEDURE NewTemp (VAR t: QVal);
@@ -379,34 +767,95 @@ PROCEDURE NewTemp (VAR t: QVal);
     INC(nTemp)
   END NewTemp;
 
-PROCEDURE LoadVar (name: ARRAY OF CHAR; isReal: BOOLEAN; VAR q: QVal);
+PROCEDURE LocLoad (idx: INTEGER; long: BOOLEAN; VAR q: QVal);
+(* Loads a local entry: slots/data by class (or long for pointers),
+   addr entries via ElemLoad. *)
   BEGIN
+    IF locTag[idx] = 2 THEN
+      ElemLoad(locRep[idx], locTyp[idx], q);
+      RETURN
+    END;
     NewTemp(q);
     Revive;
     W("  "); W(q);
-    IF isReal THEN W(" =d loadd $") ELSE W(" =w loadw $") END;
-    WL(name)
+    IF long THEN W(" =l loadl ")
+    ELSIF locCls[idx] = "d" THEN W(" =d loadd ")
+    ELSE W(" =w loadw ")
+    END;
+    IF locTag[idx] = 1 THEN W("$") END;
+    WL(locRep[idx])
+  END LocLoad;
+
+PROCEDURE LocStore (idx: INTEGER; q: ARRAY OF CHAR; long: BOOLEAN);
+(* Stores a local entry: slots/data by class (or long), addr via
+   ElemStore. Data stores are grammar-unreachable (consts). *)
+  BEGIN
+    IF locTag[idx] = 2 THEN
+      ElemStore(locRep[idx], q, locTyp[idx]);
+      RETURN
+    END;
+    Revive;
+    IF long THEN W("  storel ")
+    ELSIF locCls[idx] = "d" THEN W("  stored ")
+    ELSE W("  storew ")
+    END;
+    W(q); W(", ");
+    IF locTag[idx] = 1 THEN W("$") END;
+    WL(locRep[idx])
+  END LocStore;
+
+PROCEDURE LoadVar (name: ARRAY OF CHAR; isReal: BOOLEAN; VAR q: QVal);
+  VAR idx : INTEGER;
+  BEGIN
+    idx := LocFind(name);
+    IF idx = -1 THEN
+      NewTemp(q);
+      Revive;
+      W("  "); W(q);
+      IF isReal THEN W(" =d loadd $") ELSE W(" =w loadw $") END;
+      WL(name);
+      RETURN
+    END;
+    LocLoad(idx, FALSE, q)
   END LoadVar;
 
 PROCEDURE LoadPtr (name: ARRAY OF CHAR; VAR q: QVal);
+  VAR idx : INTEGER;
   BEGIN
-    NewTemp(q);
-    Revive;
-    W("  "); W(q); W(" =l loadl $");
-    WL(name)
+    idx := LocFind(name);
+    IF idx = -1 THEN
+      NewTemp(q);
+      Revive;
+      W("  "); W(q); W(" =l loadl $");
+      WL(name);
+      RETURN
+    END;
+    LocLoad(idx, TRUE, q)
   END LoadPtr;
 
 PROCEDURE StoreVar (name: ARRAY OF CHAR; q: ARRAY OF CHAR; isReal: BOOLEAN);
+  VAR idx : INTEGER;
   BEGIN
-    Revive;
-    IF isReal THEN W("  stored ") ELSE W("  storew ") END;
-    W(q); W(", $"); WL(name)
+    idx := LocFind(name);
+    IF idx = -1 THEN
+      Revive;
+      IF isReal THEN W("  stored ") ELSE W("  storew ") END;
+      W(q); W(", $"); WL(name);
+      RETURN
+    END;
+    LocStore(idx, q, FALSE)
   END StoreVar;
 
 PROCEDURE StorePtr (name: ARRAY OF CHAR; q: ARRAY OF CHAR);
+  VAR idx : INTEGER;
   BEGIN
-    Revive;
-    W("  storel "); W(q); W(", $"); WL(name)
+    idx := LocFind(name);
+    IF idx = -1 THEN
+      Revive;
+      W("  storel "); W(q); W(", $"); WL(name);
+      RETURN
+    END;
+    LocStore(idx, q, TRUE)
   END StorePtr;
 
 PROCEDURE Op3 (mn: ARRAY OF CHAR; res, l, r: ARRAY OF CHAR;
@@ -1327,7 +1776,10 @@ PROCEDURE NewHeap (t: SymTab.TypeIndex; VAR q: QVal);
     NewTemp(q);
     Revive;
     IntStr(VAL(INTEGER, HeapSize(t)), nb);
-    W("  "); W(q); W(" =l call $malloc(l "); W(nb); WL(")")
+    W("  "); W(q);
+    IF useStack THEN W(" =l alloc8 "); W(nb); WL("")
+    ELSE W(" =l call $malloc(l "); W(nb); WL(")")
+    END
   END NewHeap;
 
 PROCEDURE FreeHeap (v: ARRAY OF CHAR);
@@ -1393,5 +1845,7 @@ BEGIN
   inBody := FALSE;
   dead := FALSE;
   nTemp := 0; nLab := 0; loopTop := 0; nR := 0; nStr := 0;
-  withTop := 0
+  withTop := 0;
+  noEmit := FALSE; inFunc := FALSE; useStack := FALSE;
+  nLoc := 0; nPar := 0; nArg := 0; nn := 0; callDepth := 0
 END QbeGen.

+ 16 - 0
compiler/src/SymTab.def

@@ -157,6 +157,22 @@ PROCEDURE CurRes (): TypeIndex;
 (* Result type of the innermost open procedure,
    InvalidType outside procedures or for proper procedures. *)
 
+PROCEDURE ProcDepth (): CARDINAL;
+(* Number of open procedure headings/bodies (nesting depth). *)
+
+PROCEDURE ProcUid (name: ARRAY OF CHAR): CARDINAL;
+(* Unique id of the named procedure (0 if absent); drives QBE
+   name mangling so same-named methods never collide. *)
+
+PROCEDURE ProcNPar (name: ARRAY OF CHAR): CARDINAL;
+PROCEDURE ParamType (name: ARRAY OF CHAR; i: CARDINAL): TypeIndex;
+PROCEDURE ParamIsVar (name: ARRAY OF CHAR; i: CARDINAL): BOOLEAN;
+(* Positional formal access for call checking (233 on mismatch). *)
+
+PROCEDURE VarParamOk (actual, formal: TypeIndex): BOOLEAN;
+(* VAR-formal compatibility: same type, fixed array into open
+   array with same element, or string literal into open CHAR. *)
+
 (* ---------------- classes (Clarion form, lowering deferred) ---------------- *)
 
 PROCEDURE SetParent (t, p: TypeIndex);

+ 81 - 1
compiler/src/SymTab.mod

@@ -31,6 +31,7 @@ TYPE
     isVar : BOOLEAN;    (* KindParam: VAR formal *)
     fwd   : BOOLEAN;    (* KindProc: FORWARD body pending *)
     virt  : BOOLEAN;    (* KindProc: VIRTUAL method *)
+    uid   : CARDINAL;   (* KindProc: unique id for name mangling *)
   END;
   ScopeNode = RECORD
     parent : ScopePtr;  (* scope tree link *)
@@ -75,6 +76,7 @@ VAR
   curPTail : SymPtr;  (* positional param chain tail *)
   procStk : ARRAY [0 .. 15] OF SymPtr;
   nProc : CARDINAL;
+  nextUid : CARDINAL;
   dInt, dCard, dReal, dChar, dBool, dNil : TypeIndex;
 
 (* ---------------- strings ---------------- *)
@@ -902,6 +904,7 @@ PROCEDURE EnterProc (name: ARRAY OF CHAR): BOOLEAN;
     node^.isVar := FALSE;
     node^.fwd := FALSE;
     node^.virt := FALSE;
+    node^.uid := nextUid; INC(nextUid);
     curProc := node;
     curPTail := NIL;
     PushProc(node);
@@ -981,6 +984,83 @@ PROCEDURE CurRes (): TypeIndex;
     RETURN procStk[nProc - 1]^.rslt
   END CurRes;
 
+PROCEDURE ProcDepth (): CARDINAL;
+  BEGIN
+    RETURN nProc
+  END ProcDepth;
+
+PROCEDURE ProcUid (name: ARRAY OF CHAR): CARDINAL;
+  VAR node: SymPtr;
+  BEGIN
+    node := Find(name);
+    IF (node = NIL) OR (node^.kind # KindProc) THEN RETURN 0 END;
+    RETURN node^.uid
+  END ProcUid;
+
+PROCEDURE NthParam (node: SymPtr; i: CARDINAL): SymPtr;
+  BEGIN
+    node := node^.plink;
+    WHILE (i > 0) & (node # NIL) DO
+      node := node^.plink; DEC(i)
+    END;
+    RETURN node
+  END NthParam;
+
+PROCEDURE ProcNPar (name: ARRAY OF CHAR): CARDINAL;
+  VAR node, p: SymPtr;
+    n: CARDINAL;
+  BEGIN
+    node := Find(name);
+    IF (node = NIL) OR (node^.kind # KindProc) THEN RETURN 0 END;
+    n := 0; p := node^.plink;
+    WHILE p # NIL DO INC(n); p := p^.plink END;
+    RETURN n
+  END ProcNPar;
+
+PROCEDURE ParamType (name: ARRAY OF CHAR; i: CARDINAL): TypeIndex;
+  VAR node, p: SymPtr;
+  BEGIN
+    node := Find(name);
+    IF (node = NIL) OR (node^.kind # KindProc) THEN
+      RETURN InvalidType
+    END;
+    p := NthParam(node, i);
+    IF p = NIL THEN RETURN InvalidType END;
+    RETURN p^.typ
+  END ParamType;
+
+PROCEDURE ParamIsVar (name: ARRAY OF CHAR; i: CARDINAL): BOOLEAN;
+  VAR node, p: SymPtr;
+  BEGIN
+    node := Find(name);
+    IF (node = NIL) OR (node^.kind # KindProc) THEN RETURN FALSE END;
+    p := NthParam(node, i);
+    IF p = NIL THEN RETURN FALSE END;
+    RETURN p^.isVar
+  END ParamIsVar;
+
+PROCEDURE VarParamOk (actual, formal: TypeIndex): BOOLEAN;
+(* Addressable-designator compatibility for VAR formals: same type,
+   or fixed array into open array with same element type, or string
+   literal into open CHAR array. *)
+  VAR fa, fe: TypeIndex;
+  BEGIN
+    IF (actual = InvalidType) OR (formal = InvalidType) THEN
+      RETURN TRUE
+    END;
+    IF SameType(actual, formal) THEN RETURN TRUE END;
+    IF IsOpenArray(formal)
+       & (ClassOf(actual) = ClArray) & (ArrayDepth(actual) = 1) THEN
+      fa := ArrayElem(actual); fe := ArrayElem(formal);
+      IF SameType(fa, fe) THEN RETURN TRUE END
+    END;
+    IF IsOpenArray(formal) & (ClassOf(actual) = ClStr) THEN
+      fe := ArrayElem(formal);
+      IF ClassOf(fe) = ClChar THEN RETURN TRUE END
+    END;
+    RETURN FALSE
+  END VarParamOk;
+
 (* ---------------- predicates (unchanged) ---------------- *)
 
 PROCEDURE BaseSpanOk (b: TypeIndex): BOOLEAN;
@@ -1177,7 +1257,7 @@ PROCEDURE Init;
     scopeList := NIL; scopeTail := NIL;
     fields := NIL; nFields := 0;
     nPend := 0; nPendF := 0;
-    nTypes := 0; nProc := 0; nBounds := 0;
+    nTypes := 0; nProc := 0; nBounds := 0; nextUid := 0;
     curProc := NIL; curPTail := NIL;
     curScope := NewScope(NIL, 0);
     dInt := NewDesc(FInt, InvalidType);

+ 1 - 0
compiler/src/compiler.frm

@@ -105,6 +105,7 @@ MODULE -->Grammar;
         | 230: Msg("not supported yet")
         | 231: Msg("opaque type outside definition")
         | 232: Msg("bad RETURN")
+        | 233: Msg("invalid call")
         ELSE         Msg("Error: "); WriteInt(f, nr, 0);
         END
       END ErrText;

+ 46 - 0
compiler/tests/showcase9.mod

@@ -0,0 +1,46 @@
+MODULE Showcase9;
+// Step 4.1 tour: procedures, value/VAR params, recursion,
+// array + open-array params, strings through calls. Exit 140.
+TYPE Vec = ARRAY [0..3] OF INTEGER;
+VAR ExitCode : INTEGER;
+
+PROCEDURE Double(n : INTEGER) : INTEGER;
+BEGIN
+  RETURN n * 2
+END Double;
+
+PROCEDURE Acc(VAR total : INTEGER; x : INTEGER);
+BEGIN
+  total := total + x
+END Acc;
+
+PROCEDURE Sum4(a : Vec) : INTEGER;
+VAR i, s : INTEGER;
+BEGIN
+  s := 0;
+  FOR i := 0 TO 3 DO s := s + a[i] END;
+  RETURN s
+END Sum4;
+
+PROCEDURE Head(VAR a : ARRAY OF INTEGER) : INTEGER;
+BEGIN
+  RETURN a[0]
+END Head;
+
+PROCEDURE Fact(n : INTEGER) : INTEGER;
+BEGIN
+  IF n <= 1 THEN RETURN 1 END;
+  RETURN n * Fact(n - 1)
+END Fact;
+
+VAR v : Vec;
+VAR t : INTEGER;
+BEGIN
+  v[0] := 10; v[1] := 20; v[2] := 30; v[3] := 40;
+  t := 0;
+  Acc(t, Double(5));
+  Acc(t, Sum4(v));
+  Acc(t, Head(v));
+  Acc(t, Fact(5) MOD 100);
+  ExitCode := t
+END Showcase9.

+ 7 - 0
compiler/tests/t_bad_actual.mod

@@ -0,0 +1,7 @@
+MODULE TBadActual;
+PROCEDURE P(a : INTEGER);
+BEGIN
+END P;
+BEGIN
+  P(1.5)
+END TBadActual.

+ 7 - 0
compiler/tests/t_bad_arity.mod

@@ -0,0 +1,7 @@
+MODULE TBadArity;
+PROCEDURE P(a, b : INTEGER);
+BEGIN
+END P;
+BEGIN
+  P(1)
+END TBadArity.

+ 8 - 0
compiler/tests/t_bad_funstmt.mod

@@ -0,0 +1,8 @@
+MODULE TBadFunStmt;
+PROCEDURE F() : INTEGER;
+BEGIN
+  RETURN 1
+END F;
+BEGIN
+  F()
+END TBadFunStmt.

+ 18 - 0
compiler/tests/t_call.mod

@@ -0,0 +1,18 @@
+MODULE TCall;
+// Value params, function result, recursion. Exit 125.
+VAR ExitCode : INTEGER;
+
+PROCEDURE Add(a, b : INTEGER) : INTEGER;
+BEGIN
+  RETURN a + b
+END Add;
+
+PROCEDURE Fact(n : INTEGER) : INTEGER;
+BEGIN
+  IF n <= 1 THEN RETURN 1 END;
+  RETURN n * Fact(n - 1)
+END Fact;
+
+BEGIN
+  ExitCode := Add(40, 2) + Fact(5) - 37
+END TCall.

+ 17 - 0
compiler/tests/t_mutual.mod

@@ -0,0 +1,17 @@
+MODULE TMutual;
+// Mutual recursion through FORWARD. Exit 11.
+VAR ExitCode : INTEGER;
+PROCEDURE Even(n : INTEGER) : BOOLEAN; FORWARD;
+PROCEDURE Odd(n : INTEGER) : BOOLEAN;
+BEGIN
+  IF n = 0 THEN RETURN FALSE END;
+  RETURN Even(n - 1)
+END Odd;
+PROCEDURE Even(n : INTEGER) : BOOLEAN;
+BEGIN
+  IF n = 0 THEN RETURN TRUE END;
+  RETURN Odd(n - 1)
+END Even;
+BEGIN
+  IF Even(10) AND Odd(11) THEN ExitCode := 11 ELSE ExitCode := 0 END
+END TMutual.

+ 11 - 0
compiler/tests/t_nestedproc.mod

@@ -0,0 +1,11 @@
+MODULE TNestedProc;
+VAR ExitCode : INTEGER;
+PROCEDURE Outer;
+  PROCEDURE Inner;
+  BEGIN
+  END Inner;
+BEGIN
+END Outer;
+BEGIN
+  ExitCode := 0
+END TNestedProc.

+ 13 - 0
compiler/tests/t_openarr.mod

@@ -0,0 +1,13 @@
+MODULE TOpenArr;
+// Open VAR formal over a fixed actual. Exit 7.
+TYPE Vec = ARRAY [0..2] OF INTEGER;
+VAR ExitCode : INTEGER;
+VAR v : Vec;
+PROCEDURE First(VAR a : ARRAY OF INTEGER) : INTEGER;
+BEGIN
+  RETURN a[0]
+END First;
+BEGIN
+  v[0] := 7; v[1] := 8; v[2] := 9;
+  ExitCode := First(v)
+END TOpenArr.

+ 14 - 0
compiler/tests/t_valarray.mod

@@ -0,0 +1,14 @@
+MODULE TValArray;
+// Value array params copy (callee mutation invisible). Exit 6.
+TYPE Vec = ARRAY [0..2] OF INTEGER;
+VAR ExitCode : INTEGER;
+VAR v : Vec;
+PROCEDURE Fill(a : Vec);
+BEGIN
+  a[0] := 99
+END Fill;
+BEGIN
+  v[0] := 1; v[1] := 2; v[2] := 3;
+  Fill(v);
+  ExitCode := v[0] + v[1] + v[2]
+END TValArray.

+ 26 - 0
compiler/tests/t_varpar.mod

@@ -0,0 +1,26 @@
+MODULE TVarPar;
+// VAR params: swap + accumulate through addresses. Exit 40.
+VAR ExitCode : INTEGER;
+
+PROCEDURE Swap(VAR a, b : INTEGER);
+VAR t : INTEGER;
+BEGIN
+  t := a;
+  a := b;
+  b := t
+END Swap;
+
+PROCEDURE Bump(VAR x : INTEGER; by : INTEGER);
+BEGIN
+  x := x + by
+END Bump;
+
+VAR p, q : INTEGER;
+BEGIN
+  p := 10;
+  q := 20;
+  Swap(p, q);
+  Bump(p, 5);
+  Bump(q, 5);
+  ExitCode := p + q
+END TVarPar.

+ 18 - 0
compiler/tests/t_withfield.mod

@@ -0,0 +1,18 @@
+MODULE TWithField;
+// WITH-field as VAR actual. Exit 13.
+TYPE Point = RECORD x, y : INTEGER END;
+VAR ExitCode : INTEGER;
+VAR p : Point;
+PROCEDURE Bump(VAR v : INTEGER);
+BEGIN
+  v := v + 5
+END Bump;
+BEGIN
+  p.x := 1;
+  p.y := 2;
+  WITH p DO
+    Bump(x);
+    Bump(y)
+  END;
+  ExitCode := p.x + p.y
+END TWithField.

+ 6 - 2
docs/blaise-phases-map.md

@@ -10,7 +10,7 @@ rediscovered. Refresh after every major step.
 | Blaise | Status there | Us | Status here |
 |---|---|---|---|
 | 1 Bootstrap pipeline | ✅ | Step 1 (M2→QBE hello) | ✅ done (`v3-step1`) |
-| 2 Type system (classes, records, ARC, exceptions) | ✅ | Steps 2–3 (scalars ✅, arrays ✅, sets ✅, records/pointers pending) + 1.10 Clarion classes | ⚠️ partial |
+| 2 Type system (classes, records, ARC, exceptions) | ✅ | Steps 2–4.1 (scalars ✅, composites ✅, calls/frames/recursion ✅); nested/display + separate compilation = 4.2/4.3 | ✅ complete modulo deliberate outs |
 | 3 Generics + interfaces | ✅ | Explicitly OUT (R10 blueprints rejected) | ❌ deliberate |
 | 4 OPDF debug info | ✅ | Deferred (listing only) | ❌ deferred |
 | 5 Self-hosting | ✅ | Step 8 (last) | ⚠️ same mechanism, later slot |
@@ -67,4 +67,8 @@ no step lands on IR inspection alone.
 
 - 2026-09-20: map created. `plan.md` refreshed (0x-only,
   no-AST-module reality, two-grammar strategy, step-3 substeps).
-- Next refresh: after 3.4 (pointers) or any Blaise phase flip.
+- 2026-09-22: 3.5 showcase8, 3.6 pointers, 3.7 hardening (68/68);
+  Phase-2 row → near-complete (calls remaining = step 4).
+- 2026-09-22: 4.1 call emission (79/79); Phase-2 row →
+  complete modulo deliberate outs (ARC/exceptions/generics).
+- Next refresh: after 4.3 (separate compilation) or any Blaise phase flip.

+ 73 - 0
docs/summary_step4.1.md

@@ -0,0 +1,73 @@
+# 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`.

Неке датотеке нису приказане због велике количине промена