Просмотр исходного кода

lower phase slice 3 groundwork: calls, proc headers, local capture

Adds NkCall lowering (value/statement, mirroring ArgList/ActParam),
SymTab.ParamName, procedure headers in LowerDecls, Block local-decl
capture (NkProcDecl child 2), and a callee-designator fix (NkCall child 0
is a designator, not a bare ident).

Blocker (documented): SymTab.CloseProc closes the scope the inline path
lowered inside, so an end-of-unit Lower cannot resolve procedure locals/
params -> L3 does not byte-compare and stays out of CanLower.  Needs the
per-procedure lowering timing (flip) before it can complete.

Gates: suite 182/182 (L0-L2 lower_ok still pass); fixpoint OK
(3147849 bytes).  See docs/summary_two-phase-lower3-finding.md.
Eric Streit 1 неделя назад
Родитель
Сommit
85bc6b5309

+ 133 - 0
compiler/src/Lower.mod

@@ -12,6 +12,12 @@ PROCEDURE Name (n: AST.Node; VAR s: ARRAY OF CHAR);
     END
   END Name;
 
+PROCEDURE ParamName (proc: ARRAY OF CHAR; i: CARDINAL;
+                     VAR s: ARRAY OF CHAR);
+  BEGIN
+    IF NOT SymTab.ParamName(proc, i, s) THEN s[0] := CHR(0) END
+  END ParamName;
+
 PROCEDURE IsArith (op: INTEGER): BOOLEAN;
   BEGIN
     RETURN (op = AST.OpAdd) OR (op = AST.OpSub) OR (op = AST.OpMul)
@@ -60,12 +66,45 @@ PROCEDURE IsSimpleScalar (n: AST.Node): BOOLEAN;
     RETURN IsScalarClass(SymTab.ClassOf(SymTab.SymType(nm)))
   END IsSimpleScalar;
 
+PROCEDURE CalleeName (call: AST.Node; VAR nm: SymTab.Name);
+(* The callee of NkCall: child 0 is a designator (or bare ident). *)
+  VAR d: AST.Node;
+  BEGIN
+    nm[0] := CHR(0);
+    d := AST.Child(call, 0);
+    IF d = AST.NoNode THEN RETURN END;
+    IF AST.Kind(d) = AST.NkDesignator THEN d := AST.Child(d, 0) END;
+    IF (d # AST.NoNode) AND (AST.Kind(d) = AST.NkIdent) THEN Name(d, nm) END
+  END CalleeName;
+
+PROCEDURE IsCall (n: AST.Node): BOOLEAN;
+  VAR nm: SymTab.Name;
+  BEGIN
+    IF (AST.Kind(n) # AST.NkCall) OR (AST.NChild(n) < 1) THEN RETURN FALSE END;
+    CalleeName(n, nm);
+    RETURN SymTab.Lookup(nm) AND (SymTab.SymKind(nm) = SymTab.KindProc)
+  END IsCall;
+
+PROCEDURE CanCallArgs (n: AST.Node): BOOLEAN;
+  VAR nm: SymTab.Name; i: CARDINAL;
+  BEGIN
+    CalleeName(n, nm);
+    IF SymTab.ProcNPar(nm) # (AST.NChild(n) - 1) THEN RETURN FALSE END;
+    i := 1;
+    WHILE i < AST.NChild(n) DO
+      IF NOT CanExpr(AST.Child(n, i)) THEN RETURN FALSE END;
+      INC(i)
+    END;
+    RETURN TRUE
+  END CanCallArgs;
+
 PROCEDURE CanExpr (n: AST.Node): BOOLEAN;
   VAR op: INTEGER;
   BEGIN
     IF n = AST.NoNode THEN RETURN FALSE END;
     IF AST.Kind(n) = AST.NkIntLit THEN RETURN TRUE END;
     IF AST.Kind(n) = AST.NkDesignator THEN RETURN IsSimpleScalar(n) END;
+    IF AST.Kind(n) = AST.NkCall THEN RETURN IsCall(n) AND CanCallArgs(n) END;
     IF AST.Kind(n) = AST.NkUnary THEN
       op := AST.Op(n);
       IF (op # AST.OpSub) AND (op # AST.OpNot) THEN RETURN FALSE END;
@@ -121,6 +160,11 @@ PROCEDURE CanStmt (n: AST.Node): BOOLEAN;
       IF AST.Child(n, 0) = AST.NoNode THEN RETURN TRUE END;
       RETURN CanExpr(AST.Child(n, 0))
     END;
+    IF k = AST.NkReturn THEN
+      IF AST.Child(n, 0) = AST.NoNode THEN RETURN TRUE END;
+      RETURN CanExpr(AST.Child(n, 0))
+    END;
+    IF k = AST.NkCall THEN RETURN IsCall(n) AND CanCallArgs(n) END;
     RETURN FALSE
   END CanStmt;
 
@@ -141,6 +185,7 @@ PROCEDURE CanDecl (c: AST.Node): BOOLEAN;
       RETURN IsScalarClass(SymTab.ClassOf(AST.Ty(c)))
              AND (AST.Kind(AST.Child(c, 1)) = AST.NkIntLit)
     END;
+    IF AST.Kind(c) = AST.NkProcDecl THEN RETURN CanProc(c) END;
     RETURN FALSE
   END CanDecl;
 
@@ -170,6 +215,23 @@ PROCEDURE CanLower (unit: AST.Node): BOOLEAN;
     RETURN CanDecls(AST.Child(unit, 1)) AND CanStmt(AST.Child(unit, 2))
   END CanLower;
 
+PROCEDURE CanProc (proc: AST.Node): BOOLEAN;
+  VAR nm: SymTab.Name; i: CARDINAL;
+  BEGIN
+    IF AST.Op(proc) # 0 THEN RETURN FALSE END;
+    Name(AST.Child(proc, 0), nm);
+    IF NOT SymTab.Lookup(nm) THEN RETURN FALSE END;
+    IF (SymTab.ProcRes(nm) # SymTab.InvalidType)
+       AND NOT IsScalarClass(SymTab.ClassOf(SymTab.ProcRes(nm))) THEN
+      RETURN FALSE END;
+    i := 0;
+    WHILE i < SymTab.ProcNPar(nm) DO
+      IF NOT IsScalarClass(SymTab.ClassOf(SymTab.ParamType(nm, i))) THEN
+        RETURN FALSE END;
+      INC(i)
+    END;
+    RETURN CanDecls(AST.Child(proc, 2)) AND CanStmt(AST.Child(proc, 1))
+  END CanProc;
 PROCEDURE LowerDesign (n: AST.Node; VAR t: SymTab.TypeIndex; VAR k: INTEGER;
                        VAR q: QbeGen.QVal; VAR qn: SymTab.Name;
                        VAR sfx: BOOLEAN);
@@ -198,6 +260,44 @@ PROCEDURE LowerDesign (n: AST.Node; VAR t: SymTab.TypeIndex; VAR k: INTEGER;
     END
   END LowerDesign;
 
+PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
+                     VAR q: QbeGen.QVal; want: BOOLEAN);
+  VAR nm: SymTab.Name; i: CARDINAL; at, ft, res: SymTab.TypeIndex;
+      qa, qt, qe: QbeGen.QVal; mg: QbeGen.QVal; cls: CHAR; conv, isV: BOOLEAN;
+  BEGIN
+    Name(AST.Child(n, 0), nm);
+    CalleeName(n, nm);
+    QbeGen.Mangled(nm, SymTab.ProcUid(nm), mg);
+    res := SymTab.ProcRes(nm);
+    QbeGen.CallBegin(mg, res, SymTab.ProcDepthOf(nm), SymTab.IsExternal(nm));
+    i := 1;
+    WHILE i < AST.NChild(n) DO
+      LowerExpr(AST.Child(n, i), at, qe);
+      ft := SymTab.ParamType(nm, i - 1);
+      isV := SymTab.ParamIsVar(nm, i - 1);
+      IF isV THEN
+        IF QbeGen.AddrOfVal(qe, qa) THEN
+          IF NOT QbeGen.CallArg(qa, "l") THEN END
+        END
+      ELSE
+        conv := (SymTab.ClassOf(ft) = SymTab.ClReal)
+                AND SymTab.IsIntFamily(at);
+        IF conv THEN
+          QbeGen.ConvIR(qe, qt);
+          IF NOT QbeGen.CallArg(qt, "d") THEN END
+        ELSE
+          IF NOT QbeGen.CallArg(qe, QbeGen.ArgClass(ft)) THEN END
+        END
+      END;
+      INC(i)
+    END;
+    IF want THEN
+      t := res; QbeGen.CallEnd(TRUE, q)
+    ELSE
+      t := SymTab.InvalidType; QbeGen.CopyOp("0", q); QbeGen.CallEnd(FALSE, q)
+    END
+  END LowerCall;
+
 PROCEDURE LowerExpr (n: AST.Node; VAR t: SymTab.TypeIndex;
                      VAR q: QbeGen.QVal);
   VAR t2: SymTab.TypeIndex; q2, qt, qf, qs, qr: QbeGen.QVal;
@@ -209,6 +309,8 @@ PROCEDURE LowerExpr (n: AST.Node; VAR t: SymTab.TypeIndex;
       Name(n, qn); QbeGen.NormInt(qn, q); t := SymTab.IntType()
     ELSIF AST.Kind(n) = AST.NkDesignator THEN
       LowerDesign(n, t, k, q, qn, sfx)
+    ELSIF AST.Kind(n) = AST.NkCall THEN
+      LowerCall(n, t, q, TRUE)
     ELSIF AST.Kind(n) = AST.NkUnary THEN
       LowerExpr(AST.Child(n, 0), t, q);
       IF AST.Op(n) = AST.OpSub THEN
@@ -388,11 +490,25 @@ PROCEDURE LowerStmt (seq: AST.Node);
       IF AST.Child(seq, 0) # AST.NoNode THEN
         LowerExpr(AST.Child(seq, 0), t, q) END;
       QbeGen.HaltQ
+    ELSIF k = AST.NkReturn THEN
+      IF AST.Child(seq, 0) = AST.NoNode THEN
+        QbeGen.EmitRet(q, FALSE)
+      ELSE
+        LowerExpr(AST.Child(seq, 0), t, q);
+        conv := (SymTab.ClassOf(SymTab.CurRes()) = SymTab.ClReal)
+                AND SymTab.IsIntFamily(t);
+        IF conv THEN QbeGen.ConvIR(q, qt); QbeGen.EmitRet(qt, TRUE)
+        ELSE QbeGen.EmitRet(q, TRUE)
+        END
+      END
+    ELSIF k = AST.NkCall THEN
+      LowerCall(seq, t, q, FALSE)
     END
   END LowerStmt;
 
 PROCEDURE LowerDecls (seq: AST.Node);
   VAR i, j: CARDINAL; c: AST.Node; nm, txt: SymTab.Name; qv: QbeGen.QVal;
+      mg: QbeGen.QVal;
   BEGIN
     IF seq = AST.NoNode THEN RETURN END;
     i := 0;
@@ -412,6 +528,23 @@ PROCEDURE LowerDecls (seq: AST.Node);
         Name(AST.Child(c, 1), txt);
         QbeGen.NormInt(txt, qv);
         QbeGen.DeclConst(nm, qv, AST.Ty(c))
+      ELSIF AST.Kind(c) = AST.NkProcDecl THEN
+        Name(AST.Child(c, 0), nm);
+        QbeGen.Mangled(nm, SymTab.ProcUid(nm), mg);
+        QbeGen.BeginFunc(mg);
+        j := 0;
+        WHILE j < SymTab.ProcNPar(nm) DO
+          ParamName(nm, j, txt);
+          IF NOT QbeGen.FuncParam(txt, SymTab.ParamIsVar(nm, j),
+                                  SymTab.ParamType(nm, j)) THEN
+          END;
+          INC(j)
+        END;
+        QbeGen.SetFuncRes(SymTab.ProcRes(nm));
+        QbeGen.EndFuncHeader;
+        LowerDecls(AST.Child(c, 2));
+        LowerStmt(AST.Child(c, 1));
+        QbeGen.EndFunc(SymTab.ProcRes(nm))
       END;
       INC(i)
     END

+ 8 - 2
compiler/src/M2.atg

@@ -81,6 +81,10 @@ VAR
      declaration; DeclSeq folds these into an NkDeclSeq. *)
   astDecl: AST.Node;
 
+  (* The local declarations of the most recent Block (a procedure body);
+     ProcDecl stores them as the proc node's child 2. *)
+  astBlkDecls: AST.Node;
+
   (* Unit-AST root (slice 7): NkUnit(name, decls, body). *)
   astUnit: AST.Node;
 
@@ -242,7 +246,7 @@ PRODUCTIONS
     = (. AST.Init; twoPhase := TRUE; astCur := AST.NoNode; astStmt := AST.NoNode; astDecl := AST.NoNode; astImp := AST.NoNode; astImpTail := AST.NoNode;
                                            astImp := AST.NoNode;
                                            astImpTail := AST.NoNode; .)
-      Unit "." .
+      Unit "." (. FileIO.WriteString(FileIO.StdOut, "ASTDUMP:"); FileIO.WriteLn(FileIO.StdOut); AST.Dump(astUnit, 0); .) .
   (* Units: program modules compile fully; DEFINITION and
      IMPLEMENTATION modules parse + check now but lower in step 4
      (each ends with one 230); same for nested local modules. *)
@@ -1223,12 +1227,14 @@ PRODUCTIONS
                                            QbeGen.AbortFunc; .)
       |                                 (. QbeGen.EndFuncHeader; .)
         Block<pn>                       (. AST.SetChild(astNode, 1, astStmt);
+                                           AST.SetChild(astNode, 2, astBlkDecls);
                                            astDecl := astNode;
                                            SymTab.CloseProc;
                                            QbeGen.EndFunc(
                                              SymTab.ProcRes(pn)); .) ) .
   Block<pn: SymTab.Name>                (. VAR m2: SymTab.Name; .)
-    = DeclSeq                           (. astStmt := AST.NoNode; .)
+    = DeclSeq                           (. astBlkDecls := astDecl;
+                                           astStmt := AST.NoNode; .)
       [ "BEGIN"
         [ StatSeq ] ]
       "END"

Разница между файлами не показана из-за своего большого размера
+ 3708 - 3702
compiler/src/M2.lst


+ 5 - 0
compiler/src/SymTab.def

@@ -212,6 +212,11 @@ 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 ParamName (name: ARRAY OF CHAR; i: CARDINAL;
+                     VAR out: ARRAY OF CHAR): BOOLEAN;
+(* The i-th formal's name (FALSE when out of range).  Read-only helper
+   for the two-phase Lower walk. *)
+
 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. *)

+ 14 - 0
compiler/src/SymTab.mod

@@ -2191,6 +2191,20 @@ PROCEDURE ParamIsVar (name: ARRAY OF CHAR; i: CARDINAL): BOOLEAN;
     RETURN p^.isVar
   END ParamIsVar;
 
+PROCEDURE ParamName (name: ARRAY OF CHAR; i: CARDINAL;
+                     VAR out: ARRAY OF CHAR): BOOLEAN;
+  VAR node, p: SymPtr;
+  BEGIN
+    out[0] := CHR(0);
+    node := Find(name);
+    IF node = NIL THEN RETURN FALSE END;
+    IF node^.kind # KindProc THEN RETURN FALSE END;
+    p := NthParam(node, i);
+    IF p = NIL THEN RETURN FALSE END;
+    Assign(out, p^.name);
+    RETURN TRUE
+  END ParamName;
+
 
 (* ---------------- modules / separate compilation (step 4.3) ---------------- *)
 

+ 23 - 0
compiler/tests/t_lower3.mod

@@ -0,0 +1,23 @@
+MODULE TLower3;
+(* Lower L3: a function (params + result), a procedure with locals,
+   calls, and RETURN with a value.  Exit 15. *)
+VAR g : INTEGER;
+VAR ExitCode : INTEGER;
+
+PROCEDURE Add(a, b : INTEGER) : INTEGER;
+VAR s : INTEGER;
+BEGIN
+  s := a + b;
+  RETURN s
+END Add;
+
+PROCEDURE Bump(VAR x : INTEGER);
+BEGIN
+  x := x + 1
+END Bump;
+
+BEGIN
+  g := Add(4, 10);
+  Bump(g);
+  ExitCode := g
+END TLower3.

+ 62 - 0
docs/summary_two-phase-lower3-finding.md

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

Некоторые файлы не были показаны из-за большого количества измененных файлов