Ver código fonte

v3 step 4.2 — nested procedures: static-link display (82/82 tests green)

Eric Streit 2 semanas atrás
pai
commit
4383fb486d

+ 4 - 1
compiler/run_tests.sh

@@ -77,6 +77,7 @@ expect_run showcase6.mod 34
 expect_run showcase7.mod 219
 expect_run showcase8.mod 170
 expect_run showcase9.mod 140
+expect_run showcase10.mod 86
 expect_run t_pointer.mod 42
 expect_run t_ptrarray.mod 15
 expect_run t_ptrrec.mod 15
@@ -91,7 +92,9 @@ 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_run t_nestedproc.mod 20
+expect_run t_nestdeep.mod 211
+expect_run t_nestrec.mod 15
 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"

+ 37 - 25
compiler/src/M2.atg

@@ -491,6 +491,8 @@ PRODUCTIONS
                                               & (cls # SymTab.ClRecord)
                                               & (cls # SymTab.ClPtr) THEN
                                              SemError(230) END;
+                                           IF QbeGen.LocFull() THEN
+                                             SemError(233) END;
                                            i := 0;
                                            WHILE i < SymTab.PendCount() DO
                                              SymTab.PendName(i, nm);
@@ -559,31 +561,18 @@ PRODUCTIONS
                                            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> ";"
+  (* Nested procedures lower like top-level ones (4.2): the
+     static link gives them their parent's frame. Methods keep
+     parse-now/230-later. *)
+  ProcDecl                              (. VAR pn: SymTab.Name; .)
+    = ProcHeading<pn> ";"
       ( "FORWARD"                       (. SymTab.MarkFwd;
                                            SymTab.CloseProc;
-                                           QbeGen.AbortFunc;
-                                           IF nested THEN
-                                             QbeGen.SetNoEmit(FALSE);
-                                             SemError(230)
-                                           END; .)
+                                           QbeGen.AbortFunc; .)
       |                                 (. QbeGen.EndFuncHeader; .)
         Block<pn>                       (. SymTab.CloseProc;
-                                           IF nested THEN
-                                             QbeGen.AbortFunc;
-                                             QbeGen.SetNoEmit(FALSE);
-                                             SemError(230)
-                                           ELSE
-                                             QbeGen.EndFunc(
-                                               SymTab.ProcRes(pn))
-                                           END; .) ) .
+                                           QbeGen.EndFunc(
+                                             SymTab.ProcRes(pn)); .) ) .
   Block<pn: SymTab.Name>                (. VAR m2: SymTab.Name; .)
     = DeclSeq
       [ "BEGIN"
@@ -706,8 +695,8 @@ PRODUCTIONS
                                              dk: INTEGER;
                                              qd, qe, qt: QbeGen.QVal;
                                              qn: SymTab.Name;
-                                             ct2: SymTab.TypeIndex;
-                                             q2: QbeGen.QVal;
+                                             ct2, res0: SymTab.TypeIndex;
+                                             q2, mg0: QbeGen.QVal;
                                              isR, conv: BOOLEAN;
                                              called, sfx: BOOLEAN; .)
     = Design<dt, dk, qd, qn, sfx>
@@ -780,7 +769,29 @@ PRODUCTIONS
                                              QbeGen.StoreVar(qn, qe, isR)
                                            END
                                          END; .)
-      | ArgList<qn, FALSE, ct2, q2, called> ) .
+      | ArgList<qn, FALSE, ct2, q2, called>
+      |                                 (* bare `P;`: proper parameterless
+                                            procedure call; anything else
+                                            here is 233 (was a bare syntax
+                                            error before 4.2) *)
+                                        (. IF (dk = SymTab.KindProc)
+                                             & ~sfx THEN
+                                             res0 := SymTab.ProcRes(qn);
+                                             IF res0 #
+                                                SymTab.InvalidType THEN
+                                               SemError(233)
+                                             ELSIF SymTab.ProcNPar(qn) #
+                                                     0 THEN
+                                               SemError(233)
+                                             ELSE QbeGen.Mangled(qn,
+                                               SymTab.ProcUid(qn), mg0);
+                                               QbeGen.CallBegin(mg0,
+                                                 res0,
+                                                 SymTab.ProcDepthOf(qn));
+                                               QbeGen.CallEnd(FALSE, q2)
+                                             END
+                                           ELSE SemError(233)
+                                           END; .) ) .
   (* 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;
@@ -799,7 +810,8 @@ PRODUCTIONS
                                              SemError(233); ok := FALSE
                                            ELSE QbeGen.Mangled(pn,
                                              SymTab.ProcUid(pn), mg);
-                                             QbeGen.CallBegin(mg, res)
+                                             QbeGen.CallBegin(mg, res,
+                                               SymTab.ProcDepthOf(pn))
                                            END;
                                            i := 0; .)
       [ ActParam<pn, i>                  (. INC(i); .)

Diferenças do arquivo suprimidas por serem muito extensas
+ 1197 - 1185
compiler/src/M2.lst


+ 7 - 2
compiler/src/QbeGen.def

@@ -68,8 +68,10 @@ PROCEDURE EndFunc (resT: INTEGER);
 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 CallBegin (mangled: ARRAY OF CHAR; resT: INTEGER;
+                       fdep: CARDINAL);
+(* Starts accumulating a call's actuals; resolves the static link
+   from the caller's context and the callee's lexical depth. *)
 
 PROCEDURE CallArg (q: ARRAY OF CHAR; cls: CHAR): BOOLEAN;
 (* Appends one typed actual (FALSE when full → 233). *)
@@ -83,6 +85,9 @@ PROCEDURE InitStack (addr: ARRAY OF CHAR; t: INTEGER);
 PROCEDURE ArgClass (t: INTEGER): CHAR;
 (* Formal/actual class for call emission. *)
 
+PROCEDURE LocFull (): BOOLEAN;
+(* TRUE past 64 locals in the current scope (→ 233). *)
+
 PROCEDURE NewTemp (VAR t: QVal);
 (* Fresh "%tN" operand (deterministic counter: fixpoint-safe). *)
 

+ 337 - 77
compiler/src/QbeGen.mod

@@ -28,11 +28,22 @@ VAR
   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;
+  locNames : ARRAY [0 .. 255] OF SymTab.Name;
+  locTag : ARRAY [0 .. 255] OF INTEGER;  (* 0 slot, 1 data, 2 addr *)
+  locRep : ARRAY [0 .. 255] OF QVal;     (* slot temp / mangled / addr *)
+  locCls : ARRAY [0 .. 255] OF CHAR;
+  locTyp : ARRAY [0 .. 255] OF INTEGER;
+  funcDepth : CARDINAL;  (* open BeginFuncs; main body = 0 *)
+  scopeTop : CARDINAL;   (* open procedure scopes *)
+  scopeBase : ARRAY [0 .. 16] OF CARDINAL;
+  scopeLink : ARRAY [0 .. 15] OF QVal;  (* per-scope link records *)
+  slTmp : QVal;          (* current static-link param temp *)
+  hdrComma : BOOLEAN;
+  stkLink : ARRAY [0 .. 15] OF QVal;    (* per-call static links *)
+  outSel : CARDINAL;  (* 0 = file, else nestBufs[outSel-1] *)
+  nestBufs : ARRAY [0 .. 15] OF ARRAY [0 .. 65535] OF CHAR;
+  (* one buffer per nesting depth: each nested function stays
+     contiguous no matter how deep the parse interleaves *)
   nPar : CARDINAL;   (* recorded formal params for the header *)
   parCls : ARRAY [0 .. 63] OF CHAR;
   parTmp : ARRAY [0 .. 63] OF QVal;
@@ -84,15 +95,40 @@ PROCEDURE App (VAR d: ARRAY OF CHAR; s: ARRAY OF CHAR);
     IF i <= HIGH(d) THEN d[i] := 0C END
   END App;
 
+PROCEDURE WEmit (s: ARRAY OF CHAR; eol: BOOLEAN);
+(* Single output sink: file, or the nest buffer for nested
+   functions (QBE rejects definitions inside functions, so nested
+   bodies buffer until EndModule). *)
+  VAR i, j, b : CARDINAL;
+  BEGIN
+    IF ~opened OR noEmit THEN RETURN END;
+    IF outSel = 0 THEN
+      FileIO.WriteString(out, s);
+      IF eol THEN FileIO.WriteLn(out) END;
+      RETURN
+    END;
+    b := outSel - 1;
+    IF b > HIGH(nestBufs) THEN b := HIGH(nestBufs) END;
+    i := 0;
+    WHILE (i <= HIGH(nestBufs[b])) & (nestBufs[b][i] # 0C) DO INC(i) END;
+    j := 0;
+    WHILE (i < HIGH(nestBufs[b])) & (j <= HIGH(s)) & (s[j] # 0C) DO
+      nestBufs[b][i] := s[j]; INC(i); INC(j)
+    END;
+    IF eol & (i < HIGH(nestBufs[b])) THEN
+      nestBufs[b][i] := CHR(10); INC(i)
+    END;
+    IF i <= HIGH(nestBufs[b]) THEN nestBufs[b][i] := 0C END
+  END WEmit;
+
 PROCEDURE W (s: ARRAY OF CHAR);
   BEGIN
-    IF opened & ~noEmit THEN FileIO.WriteString(out, s) END
+    WEmit(s, FALSE)
   END W;
 
 PROCEDURE WL (s: ARRAY OF CHAR);
   BEGIN
-    W(s);
-    IF opened & ~noEmit THEN FileIO.WriteLn(out) END
+    WEmit(s, TRUE)
   END WL;
 
 PROCEDURE SetNoEmit (b: BOOLEAN);
@@ -102,8 +138,10 @@ PROCEDURE SetNoEmit (b: BOOLEAN);
 
 PROCEDURE Wc (c: CHAR);
 (* Writes a single class character (w/d/l). *)
+  VAR s : ARRAY [0 .. 1] OF CHAR;
   BEGIN
-    IF opened & ~noEmit THEN FileIO.Write(out, c) END
+    s[0] := c; s[1] := 0C;
+    W(s)
   END Wc;
 
 PROCEDURE AppNum (VAR d: ARRAY OF CHAR; v: CARDINAL);
@@ -158,19 +196,23 @@ PROCEDURE AddrOfVal (val: ARRAY OF CHAR; VAR addr: QVal): BOOLEAN;
 
 PROCEDURE AddrOf (name: ARRAY OF CHAR; VAR q: QVal);
   VAR idx : INTEGER;
+    levels : CARDINAL;
   BEGIN
-    idx := LocFind(name);
-    IF idx = -1 THEN
-      Cpy(q, "$");
-      App(q, name);
+    IF LocFindUp(name, levels, idx) THEN
+      IF levels = 0 THEN
+        IF locTag[idx] = 1 THEN
+          Cpy(q, "$");
+          App(q, locRep[idx])
+        ELSE
+          Cpy(q, locRep[idx])
+        END
+      ELSE
+        UpAddrOf(idx, levels, q)
+      END;
       RETURN
     END;
-    IF locTag[idx] = 1 THEN
-      Cpy(q, "$");
-      App(q, locRep[idx])
-    ELSE
-      Cpy(q, locRep[idx])
-    END
+    Cpy(q, "$");
+    App(q, name)
   END AddrOf;
 
 PROCEDURE IntStr (v: INTEGER; VAR s: QVal);
@@ -311,6 +353,7 @@ PROCEDURE IsImm (s: ARRAY OF CHAR): BOOLEAN;
 
 PROCEDURE OpenModule (name: ARRAY OF CHAR);
   VAR fname : ARRAY [0 .. 127] OF CHAR;
+    i : CARDINAL;
   BEGIN
     fname[0] := 0C;
     App(fname, "gen_ssa/");
@@ -324,6 +367,10 @@ PROCEDURE OpenModule (name: ARRAY OF CHAR);
     withTop := 0;
     noEmit := FALSE; inFunc := FALSE; useStack := FALSE;
     nLoc := 0; nPar := 0; nArg := 0; nn := 0; callDepth := 0;
+    funcDepth := 0; scopeTop := 0; scopeBase[0] := 0;
+    outSel := 0;
+    i := 0;
+    WHILE i <= HIGH(nestBufs) DO nestBufs[i][0] := 0C; INC(i) END;
     WL("# QBE IR generated by the V3 step-1 backend");
     WL("")
   END OpenModule;
@@ -447,6 +494,7 @@ PROCEDURE CloseModule;
   END CloseModule;
 
 PROCEDURE EndModule;
+  VAR i : CARDINAL;
   BEGIN
     IF ~opened THEN RETURN END;
     IF ~inBody THEN BeginBody END;
@@ -459,6 +507,13 @@ PROCEDURE EndModule;
       WL("  ret 0")
     END;
     WL("}");
+    i := 0;
+    WHILE i <= HIGH(nestBufs) DO
+      IF nestBufs[i][0] # 0C THEN
+        FileIO.WriteString(out, nestBufs[i])
+      END;
+      INC(i)
+    END;
     FlushStrings;
     FileIO.Close(out);
     opened := FALSE
@@ -481,19 +536,105 @@ PROCEDURE LocFind (name: ARRAY OF CHAR): INTEGER;
     RETURN -1
   END LocFind;
 
+PROCEDURE ScopeBegin;
+(* Pushes a procedure scope (always balanced, even under NoEmit). *)
+  BEGIN
+    IF scopeTop > HIGH(scopeLink) THEN RETURN END;
+    scopeBase[scopeTop] := nLoc;
+    INC(scopeTop);
+    scopeBase[scopeTop] := nLoc
+  END ScopeBegin;
+
+PROCEDURE ScopeEnd;
+  BEGIN
+    IF scopeTop > 0 THEN
+      DEC(scopeTop);
+      nLoc := scopeBase[scopeTop]
+    END
+  END ScopeEnd;
+
+PROCEDURE LocFindUp (name: ARRAY OF CHAR; VAR levels: CARDINAL;
+                      VAR flat: INTEGER): BOOLEAN;
+(* Innermost procedure scope holding name; levels = scopes crossed,
+   flat = table index. FALSE when purely global. *)
+  VAR s, top, lo, hi, j : CARDINAL;
+  BEGIN
+    levels := 0; flat := -1;
+    IF scopeTop = 0 THEN RETURN FALSE END;
+    top := scopeTop - 1;
+    s := top;
+    LOOP
+      lo := scopeBase[s];
+      IF s = top THEN hi := nLoc ELSE hi := scopeBase[s + 1] END;
+      j := lo;
+      WHILE j < hi DO
+        IF SymTab.Equal(locNames[j], name) THEN
+          levels := top - s;
+          flat := VAL(INTEGER, j);
+          RETURN TRUE
+        END;
+        INC(j)
+      END;
+      IF s = 0 THEN RETURN FALSE END;
+      DEC(s)
+    END
+  END LocFindUp;
+
 PROCEDURE LocAdd (name: ARRAY OF CHAR; tag: INTEGER; rep: ARRAY OF CHAR;
                    cls: CHAR; typ: INTEGER);
+  VAR idx : CARDINAL;
+    off : QVal;
+    lk : QVal;
   BEGIN
     IF noEmit THEN RETURN END;
-    IF nLoc > HIGH(locNames) THEN RETURN END;
+    IF (scopeTop = 0) OR (nLoc > HIGH(locNames)) THEN RETURN END;
+    idx := nLoc - scopeBase[scopeTop - 1];
+    IF idx > 63 THEN RETURN END;
     Cpy(locNames[nLoc], name);
     locTag[nLoc] := tag;
     Cpy(locRep[nLoc], rep);
     locCls[nLoc] := cls;
     locTyp[nLoc] := typ;
-    INC(nLoc)
+    INC(nLoc);
+    IF tag = 1 THEN RETURN END;
+    Cpy(lk, scopeLink[scopeTop - 1]);
+    IntStr(VAL(INTEGER, 8 * (idx + 1)), off);
+    NewTemp(lk);
+    Op3L("add", lk, scopeLink[scopeTop - 1], off);
+    Revive;
+    W("  storel "); W(rep); W(", "); WL(lk)
   END LocAdd;
 
+PROCEDURE LocFull (): BOOLEAN;
+(* TRUE past 64 locals in the current scope (→ 233). *)
+  BEGIN
+    IF scopeTop = 0 THEN RETURN FALSE END;
+    RETURN nLoc - scopeBase[scopeTop - 1] > 63
+  END LocFull;
+
+PROCEDURE UpAddr (levels: CARDINAL; rel: CARDINAL; VAR q: QVal);
+(* Address of an up-level local: walks the static chain, then
+   loads the link cell (slot address or actual address). *)
+  VAR cur, t, off : QVal;
+    L : CARDINAL;
+  BEGIN
+    Cpy(cur, scopeLink[scopeTop - 1]);
+    L := levels;
+    WHILE L > 0 DO
+      NewTemp(t);
+      Revive;
+      W("  "); W(t); W(" =l loadl "); WL(cur);
+      Cpy(cur, t);
+      DEC(L)
+    END;
+    IntStr(VAL(INTEGER, 8 * (rel + 1)), off);
+    NewTemp(t);
+    Op3L("add", t, cur, off);
+    NewTemp(q);
+    Revive;
+    W("  "); W(q); W(" =l loadl "); WL(t)
+  END UpAddr;
+
 PROCEDURE ResClass (t: INTEGER): CHAR;
   VAR cls : INTEGER;
   BEGIN
@@ -553,15 +694,29 @@ PROCEDURE AllocLocal (t: INTEGER; VAR slot: QVal);
   END AllocLocal;
 
 PROCEDURE BeginFunc (mangled: ARRAY OF CHAR);
-(* Opens function context; the header is buffered until
-   EndFuncHeader (FORWARD/DefUnit paths discard via AbortFunc). *)
+(* Opens function context and scope; the header (with a leading
+   static-link param) buffers until EndFuncHeader. FORWARD/DefUnit
+   paths discard via AbortFunc. *)
   BEGIN
+    ScopeBegin;
+    INC(funcDepth);
     inFunc := TRUE;
-    nLoc := 0;
     nPar := 0;
     funcRes := "w";
     Cpy(funcName, mangled);
-    hbuf[0] := 0C
+    hbuf[0] := 0C;
+    NewTemp(slTmp);
+    HApp("l ");
+    HApp(slTmp);
+    hdrComma := TRUE;
+    IF funcDepth > 1 THEN
+      outSel := funcDepth - 1;
+      IF outSel > HIGH(nestBufs) + 1 THEN
+        outSel := HIGH(nestBufs) + 1
+      END
+    ELSE
+      outSel := 0
+    END
   END BeginFunc;
 
 PROCEDURE SetFuncRes (t: INTEGER);
@@ -597,7 +752,8 @@ PROCEDURE FuncParam (name: ARRAY OF CHAR; isVar: BOOLEAN;
     Cpy(parNam[nPar], name);
     parVar[nPar] := isVar;
     parTyp[nPar] := t;
-    IF nPar > 0 THEN HApp(", ") END;
+    IF hdrComma THEN HApp(", ") END;
+    hdrComma := TRUE;
     buf[0] := c; buf[1] := " "; buf[2] := 0C;
     App(buf, tmp);
     HApp(buf);
@@ -606,9 +762,10 @@ PROCEDURE FuncParam (name: ARRAY OF CHAR; isVar: BOOLEAN;
   END FuncParam;
 
 PROCEDURE EndFuncHeader;
-(* Flushes the buffered header + body label + entry copies. *)
+(* Flushes the buffered header + body label, allocates the link
+   record ([0] = parent link), then emits entry copies. *)
   VAR i : CARDINAL;
-    slot, nb : QVal;
+    slot, nb, lk : QVal;
     cls : INTEGER;
   BEGIN
     W("export function "); Wc(funcRes); W(" $"); W(funcName); W("(");
@@ -616,6 +773,11 @@ PROCEDURE EndFuncHeader;
     WL(") {");
     WL("@start");
     inBody := TRUE;
+    NewTemp(lk);
+    Cpy(scopeLink[scopeTop - 1], lk);
+    Revive;
+    W("  "); W(lk); W(" =l alloc8 520"); WL("");
+    W("  storel "); W(slTmp); W(", "); WL(lk);
     i := 0;
     WHILE i < nPar DO
       cls := SymTab.ClassOf(parTyp[i]);
@@ -648,12 +810,16 @@ PROCEDURE EndFuncHeader;
 
 PROCEDURE AbortFunc;
 (* Discards function context without emitting (DefUnit headings,
-   FORWARD, methods, nested-in-4.1): resets inFunc and params. *)
+   FORWARD, methods): pops the scope, restores the depth. *)
   BEGIN
-    inFunc := FALSE;
+    ScopeEnd;
+    IF funcDepth > 0 THEN DEC(funcDepth) END;
+    inFunc := funcDepth > 0;
     inBody := FALSE;
     nPar := 0;
-    nLoc := 0
+    IF funcDepth > 1 THEN outSel := funcDepth - 1
+    ELSE outSel := 0
+    END
   END AbortFunc;
 
 PROCEDURE EndFunc (resT: INTEGER);
@@ -666,8 +832,13 @@ PROCEDURE EndFunc (resT: INTEGER);
     ELSE WL("  ret 0")
     END;
     WL("}");
-    inFunc := FALSE;
-    inBody := FALSE
+    ScopeEnd;
+    IF funcDepth > 0 THEN DEC(funcDepth) END;
+    inFunc := funcDepth > 0;
+    inBody := FALSE;
+    IF funcDepth > 1 THEN outSel := funcDepth - 1
+    ELSE outSel := 0
+    END
   END EndFunc;
 
 PROCEDURE EmitRet (q: ARRAY OF CHAR; hasVal: BOOLEAN);
@@ -679,15 +850,34 @@ PROCEDURE EmitRet (q: ARRAY OF CHAR; hasVal: BOOLEAN);
     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
+PROCEDURE CallBegin (mangled: ARRAY OF CHAR; resT: INTEGER;
+                       fdep: CARDINAL);
+(* Pushes a call level; the static link is resolved now (caller
+   context cannot change mid-call): module callers pass 0, others
+   walk the chain (inFuncDepth - fdep) from their link record. *)
+  VAR walks : INTEGER;
+    cur, t : QVal;
+  BEGIN
+    IF callDepth > HIGH(stkName) THEN RETURN END;
+    Cpy(stkName[callDepth], mangled);
+    stkRes[callDepth] := ResClass(resT);
+    stkArg[callDepth][0] := 0C;
+    stkN[callDepth] := 0;
+    IF funcDepth = 0 THEN
+      Cpy(stkLink[callDepth], "0")
+    ELSE
+      walks := VAL(INTEGER, funcDepth) - VAL(INTEGER, fdep);
+      Cpy(cur, scopeLink[scopeTop - 1]);
+      WHILE walks > 0 DO
+        NewTemp(t);
+        Revive;
+        W("  "); W(t); W(" =l loadl "); WL(cur);
+        Cpy(cur, t);
+        DEC(walks)
+      END;
+      Cpy(stkLink[callDepth], cur)
+    END;
+    INC(callDepth)
   END CallBegin;
 
 PROCEDURE CallArg (q: ARRAY OF CHAR; cls: CHAR): BOOLEAN;
@@ -742,7 +932,9 @@ PROCEDURE CallEnd (wantRes: BOOLEAN; VAR q: QVal);
     ELSE
       W("  ")
     END;
-    W(" call $"); W(funcName); W("("); W(body); WL(")")
+    W(" call $"); W(funcName); W("(l "); W(stkLink[d]);
+    IF body[0] # 0C THEN W(", "); W(body) END;
+    WL(")")
   END CallEnd;
 
 PROCEDURE InitStack (addr: ARRAY OF CHAR; t: INTEGER);
@@ -767,9 +959,9 @@ PROCEDURE NewTemp (VAR t: QVal);
     INC(nTemp)
   END NewTemp;
 
-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. *)
+PROCEDURE LocLoadFlat (idx: INTEGER; long: BOOLEAN; VAR q: QVal);
+(* Loads a same-scope 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);
@@ -784,11 +976,11 @@ PROCEDURE LocLoad (idx: INTEGER; long: BOOLEAN; VAR q: QVal);
     END;
     IF locTag[idx] = 1 THEN W("$") END;
     WL(locRep[idx])
-  END LocLoad;
+  END LocLoadFlat;
 
-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). *)
+PROCEDURE LocStoreFlat (idx: INTEGER; q: ARRAY OF CHAR; long: BOOLEAN);
+(* Stores a same-scope 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]);
@@ -802,60 +994,126 @@ PROCEDURE LocStore (idx: INTEGER; q: ARRAY OF CHAR; long: BOOLEAN);
     W(q); W(", ");
     IF locTag[idx] = 1 THEN W("$") END;
     WL(locRep[idx])
-  END LocStore;
+  END LocStoreFlat;
 
-PROCEDURE LoadVar (name: ARRAY OF CHAR; isReal: BOOLEAN; VAR q: QVal);
-  VAR idx : INTEGER;
+PROCEDURE UpLoad (flat: INTEGER; levels: CARDINAL; long: BOOLEAN;
+                   VAR q: QVal);
+(* Up-level load: data entries live globally; otherwise the link
+   cell yields the address and ElemLoad reads through it. *)
+  VAR a : QVal;
+    rel : CARDINAL;
   BEGIN
-    idx := LocFind(name);
-    IF idx = -1 THEN
+    IF locTag[flat] = 1 THEN
       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[flat] = "d" THEN W(" =d loadd $")
+      ELSE W(" =w loadw $")
+      END;
+      WL(locRep[flat]);
+      RETURN
+    END;
+    rel := VAL(CARDINAL, flat) - scopeBase[scopeTop - 1 - levels];
+    UpAddr(levels, rel, a);
+    ElemLoad(a, locTyp[flat], q)
+  END UpLoad;
+
+PROCEDURE UpStore (flat: INTEGER; levels: CARDINAL; q: ARRAY OF CHAR;
+                    long: BOOLEAN);
+  VAR a : QVal;
+    rel : CARDINAL;
+  BEGIN
+    IF locTag[flat] = 1 THEN
+      Revive;
+      IF long THEN W("  storel ")
+      ELSIF locCls[flat] = "d" THEN W("  stored ")
+      ELSE W("  storew ")
+      END;
+      W(q); W(", $"); WL(locRep[flat]);
+      RETURN
+    END;
+    rel := VAL(CARDINAL, flat) - scopeBase[scopeTop - 1 - levels];
+    UpAddr(levels, rel, a);
+    ElemStore(a, q, locTyp[flat])
+  END UpStore;
+
+PROCEDURE UpAddrOf (flat: INTEGER; levels: CARDINAL; VAR q: QVal);
+(* Up-level address: data entries name globals; otherwise the
+   link cell holds it (composites) or points at it. For scalars
+   the cell IS the usable address (slot or actual address). *)
+  VAR a : QVal;
+    rel : CARDINAL;
+  BEGIN
+    IF locTag[flat] = 1 THEN
+      Cpy(q, "$");
+      App(q, locRep[flat]);
       RETURN
     END;
-    LocLoad(idx, FALSE, q)
+    rel := VAL(CARDINAL, flat) - scopeBase[scopeTop - 1 - levels];
+    UpAddr(levels, rel, q)
+  END UpAddrOf;
+
+PROCEDURE LoadVar (name: ARRAY OF CHAR; isReal: BOOLEAN; VAR q: QVal);
+  VAR idx : INTEGER;
+    levels : CARDINAL;
+  BEGIN
+    IF LocFindUp(name, levels, idx) THEN
+      IF levels = 0 THEN LocLoadFlat(idx, FALSE, q)
+      ELSE UpLoad(idx, levels, FALSE, q)
+      END;
+      RETURN
+    END;
+    NewTemp(q);
+    Revive;
+    W("  "); W(q);
+    IF isReal THEN W(" =d loadd $") ELSE W(" =w loadw $") END;
+    WL(name)
   END LoadVar;
 
 PROCEDURE LoadPtr (name: ARRAY OF CHAR; VAR q: QVal);
   VAR idx : INTEGER;
+    levels : CARDINAL;
   BEGIN
-    idx := LocFind(name);
-    IF idx = -1 THEN
-      NewTemp(q);
-      Revive;
-      W("  "); W(q); W(" =l loadl $");
-      WL(name);
+    IF LocFindUp(name, levels, idx) THEN
+      IF levels = 0 THEN LocLoadFlat(idx, TRUE, q)
+      ELSE UpLoad(idx, levels, TRUE, q)
+      END;
       RETURN
     END;
-    LocLoad(idx, TRUE, q)
+    NewTemp(q);
+    Revive;
+    W("  "); W(q); W(" =l loadl $");
+    WL(name)
   END LoadPtr;
 
 PROCEDURE StoreVar (name: ARRAY OF CHAR; q: ARRAY OF CHAR; isReal: BOOLEAN);
   VAR idx : INTEGER;
+    levels : CARDINAL;
   BEGIN
-    idx := LocFind(name);
-    IF idx = -1 THEN
-      Revive;
-      IF isReal THEN W("  stored ") ELSE W("  storew ") END;
-      W(q); W(", $"); WL(name);
+    IF LocFindUp(name, levels, idx) THEN
+      IF levels = 0 THEN LocStoreFlat(idx, q, FALSE)
+      ELSE UpStore(idx, levels, q, FALSE)
+      END;
       RETURN
     END;
-    LocStore(idx, q, FALSE)
+    Revive;
+    IF isReal THEN W("  stored ") ELSE W("  storew ") END;
+    W(q); W(", $"); WL(name)
   END StoreVar;
 
 PROCEDURE StorePtr (name: ARRAY OF CHAR; q: ARRAY OF CHAR);
   VAR idx : INTEGER;
+    levels : CARDINAL;
   BEGIN
-    idx := LocFind(name);
-    IF idx = -1 THEN
-      Revive;
-      W("  storel "); W(q); W(", $"); WL(name);
+    IF LocFindUp(name, levels, idx) THEN
+      IF levels = 0 THEN LocStoreFlat(idx, q, TRUE)
+      ELSE UpStore(idx, levels, q, TRUE)
+      END;
       RETURN
     END;
-    LocStore(idx, q, TRUE)
+    Revive;
+    W("  storel "); W(q); W(", $"); WL(name)
   END StorePtr;
 
 PROCEDURE Op3 (mn: ARRAY OF CHAR; res, l, r: ARRAY OF CHAR;
@@ -1847,5 +2105,7 @@ BEGIN
   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
+  nLoc := 0; nPar := 0; nArg := 0; nn := 0; callDepth := 0;
+  funcDepth := 0; scopeTop := 0; scopeBase[0] := 0;
+  outSel := 0
 END QbeGen.

+ 5 - 0
compiler/src/SymTab.def

@@ -160,6 +160,11 @@ PROCEDURE CurRes (): TypeIndex;
 PROCEDURE ProcDepth (): CARDINAL;
 (* Number of open procedure headings/bodies (nesting depth). *)
 
+PROCEDURE ProcDepthOf (name: ARRAY OF CHAR): CARDINAL;
+(* Lexical function-nesting depth of the named procedure
+   (enclosing procedures; 0 = module level). Drives the static-
+   link walk count at call sites. *)
+
 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. *)

+ 10 - 0
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 *)
+    fdep  : CARDINAL;   (* KindProc: lexical function-nesting depth *)
     uid   : CARDINAL;   (* KindProc: unique id for name mangling *)
   END;
   ScopeNode = RECORD
@@ -905,6 +906,7 @@ PROCEDURE EnterProc (name: ARRAY OF CHAR): BOOLEAN;
     node^.fwd := FALSE;
     node^.virt := FALSE;
     node^.uid := nextUid; INC(nextUid);
+    node^.fdep := nProc;
     curProc := node;
     curPTail := NIL;
     PushProc(node);
@@ -989,6 +991,14 @@ PROCEDURE ProcDepth (): CARDINAL;
     RETURN nProc
   END ProcDepth;
 
+PROCEDURE ProcDepthOf (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^.fdep
+  END ProcDepthOf;
+
 PROCEDURE ProcUid (name: ARRAY OF CHAR): CARDINAL;
   VAR node: SymPtr;
   BEGIN

+ 36 - 0
compiler/tests/showcase10.mod

@@ -0,0 +1,36 @@
+MODULE Showcase10;
+// Step 4.2 tour: nested procedures, static-link display,
+// siblings sharing parent state, nested recursion, up-level
+// writes, calls. Exit 86.
+VAR ExitCode : INTEGER;
+
+PROCEDURE Calc(base : INTEGER);
+VAR acc : INTEGER;
+  PROCEDURE AddN(n : INTEGER);
+  BEGIN
+    acc := acc + n
+  END AddN;
+  PROCEDURE Run(k : INTEGER);
+  VAR inner : INTEGER;
+    PROCEDURE Down(m : INTEGER);
+    BEGIN
+      IF m <= 0 THEN RETURN END;
+      inner := inner + m;
+      Down(m - 1)
+    END Down;
+  BEGIN
+    inner := 0;
+    Down(k);
+    acc := acc + inner
+  END Run;
+BEGIN
+  acc := base;
+  AddN(10);
+  Run(4);
+  Run(3);
+  ExitCode := acc
+END Calc;
+
+BEGIN
+  Calc(60)
+END Showcase10.

+ 29 - 0
compiler/tests/t_nestdeep.mod

@@ -0,0 +1,29 @@
+MODULE TNestDeep;
+// Three levels + siblings sharing parent locals. Exit 211.
+VAR ExitCode : INTEGER;
+PROCEDURE A(v : INTEGER);
+VAR s : INTEGER;
+  PROCEDURE B;
+  VAR t : INTEGER;
+    PROCEDURE C;
+    BEGIN
+      s := s + v + t
+    END C;
+  BEGIN
+    t := 100;
+    C;
+    C
+  END B;
+  PROCEDURE D;
+  BEGIN
+    s := s + 1
+  END D;
+BEGIN
+  s := 0;
+  B;
+  D;
+  ExitCode := s
+END A;
+BEGIN
+  A(5)
+END TNestDeep.

+ 9 - 2
compiler/tests/t_nestedproc.mod

@@ -1,11 +1,18 @@
 MODULE TNestedProc;
+// Nested procedure reading/writing parent locals. Exit 20.
 VAR ExitCode : INTEGER;
-PROCEDURE Outer;
+PROCEDURE Outer(x : INTEGER);
+VAR y : INTEGER;
   PROCEDURE Inner;
   BEGIN
+    y := y + x
   END Inner;
 BEGIN
+  y := 10;
+  Inner;
+  Inner;
+  ExitCode := y
 END Outer;
 BEGIN
-  ExitCode := 0
+  Outer(5)
 END TNestedProc.

+ 19 - 0
compiler/tests/t_nestrec.mod

@@ -0,0 +1,19 @@
+MODULE TNestRec;
+// Nested recursion + up-level accumulator. Exit 15.
+VAR ExitCode : INTEGER;
+PROCEDURE Walk(n : INTEGER);
+VAR acc : INTEGER;
+  PROCEDURE Down(k : INTEGER);
+  BEGIN
+    IF k <= 0 THEN RETURN END;
+    acc := acc + k;
+    Down(k - 1)
+  END Down;
+BEGIN
+  acc := 0;
+  Down(n);
+  ExitCode := acc
+END Walk;
+BEGIN
+  Walk(5)
+END TNestRec.

+ 2 - 1
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–4.1 (scalars ✅, composites ✅, calls/frames/recursion ✅); nested/display + separate compilation = 4.2/4.3 | ✅ complete modulo deliberate outs |
+| 2 Type system (classes, records, ARC, exceptions) | ✅ | Steps 2–4.2 (scalars ✅, composites ✅, calls/frames/recursion ✅, nested + display ✅); separate compilation = 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 |
@@ -71,4 +71,5 @@ no step lands on IR inspection alone.
   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).
+- 2026-09-22: 4.2 nested procedures + static-link display (81/81).
 - Next refresh: after 4.3 (separate compilation) or any Blaise phase flip.

+ 78 - 0
docs/summary_step4.2.md

@@ -0,0 +1,78 @@
+# V3 step 4.2 — nested procedures: static-link display (done 2026-09-22)
+
+Suite 81/81 (39 run — incl. 4 trap-aborts — + 42 reject).
+LL(1)-clean (only the known-benign TypeBlock advisory).
+Nested/recursive/module-level all lower; methods stay 230.
+
+## What lowers now
+
+- **Nested procedures** (any depth) with full up-level access:
+  reads, writes, addresses, calls, recursion. Lexical scoping is
+  honest: inner scopes are entered/exited per procedure, and
+  name lookup walks the scope chain outward.
+- **Sibling procedures** sharing a parent's locals (both `B` and
+  `D` mutate `A`'s `s`), three-level nesting (`A > B > C`), and
+  nested recursion (`Down` calls itself through the display).
+
+## How the display works (Chappel / classic Wirth)
+
+- Every procedure takes a hidden leading static-link parameter
+  `l`. Its entry allocates an 8-byte-per-slot link record
+  (`alloc8 520`, 64 slots + parent link) and stores the incoming
+  link at `[0]`.
+- `LocAdd` (locals + params) stores each slot address into the
+  record: `[1..]`. `AddrOf` of a same-scope var returns the slot;
+  of an up-level var walks the chain.
+- **Up-level access** (`LocFindUp` returns levels + flat index):
+  walk `loadl` `levels` times from the current link, then load
+  the link cell. For *composites* the cell holds the address;
+  for *scalars* the cell holds the slot address (so indexing/
+  deref compose unchanged).
+- **Call sites** resolve the link at `CallBegin` time (the
+  caller context can't change mid-call): module callers pass `0`;
+  others walk `funcDepth - calleeDepth` links. Depth comes from
+  `SymTab.ProcDepthOf` (lexical, recorded at heading time).
+
+## Backend problem and fix (the section's hard-won lesson)
+
+QBE rejects a function definition nested inside a function body,
+but the parser reaches `Inner`'s `BEGIN` while still inside
+`Outer`'s body. First fix: buffer nested-function text and emit
+it after `main`. **That failed for 3 levels**: a single shared
+buffer preserved *parse* order, so `C` (parsed inside `B`)
+landed textually inside `B`'s emitted body → "label expected".
+Cure: **one buffer per nesting depth** (`nestBufs[0..15]`), so
+each function's own text stays contiguous regardless of how the
+parse interleaves; `EndModule` flushes them in depth order.
+`outSel = funcDepth` is derived, not counted (AbortFunc and
+EndFunc both restore it), which removed the stale-flag class of
+bug entirely.
+
+## Grammar notes
+
+- `ProcDecl` no longer sets `NoEmit` for nesting (methods still
+  do); the `EndFuncHeader` flush is at the `Block` boundary so
+  FORWARD/DefUnit paths discard cleanly via `AbortFunc`.
+- **Bare `P;` calls** (parameterless proper procedures) are now
+  legal (`AssOrCall` third alternative); they were a syntax error
+  before. `Inner;` in Modula-2 style finally parses. `F();`
+  (function-as-statement) and `P(args);` with wrong arity stay 233.
+- `LocFull` guard → 233 past 64 locals per scope.
+
+## SymTab (additive)
+
+`fdep` per procedure (lexical depth at heading), `ProcDepthOf`.
+Nothing else changed — the scope tree from step 1.1 already
+modelled nesting, so name lookup needed no work.
+
+## Tests
+
+`t_nestedproc` → 20 (inner reads+writes parent locals, called
+twice), `t_nestdeep` → 211 (three levels + siblings sharing the
+parent, proven by the output), `t_nestrec` → 15 (nested
+recursion); `t_nestedproc` promoted from 230-reject to a real
+run test. Total 81.
+
+Deferred: methods still 230 (class lowering), procedure-type
+variables, separate compilation + opaque completion + `HIGH`/
+`LEN` (4.3), >64 locals, `F()^` result suffixes.

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff