Ver Fonte

lower L7: working per-unit flip, whole-session tables + hash index

The bad=T that blocked per-unit Lower was the pre-existing invalid-call
errors in Lower.mod (forward refs), not the flip.  With those fixed the
per-unit mechanism works end to end:

- Driver: BeginSession/BeginUnit/EndSession + LowerUnitNow per unit
  (RestoreSession, emit, re-suppress).  QbeGen.EndModule is a no-op
  under NoEmit so the grammar neither flushes nor writes.
- The suppressed inline emit is a faithful shadow of the legacy emit
  (LocAdd/FuncParam allocate temps under NoEmit), so NoteAddr and the
  temp/label counters line up with Lower's re-emit.
- Whole-session tables (MaxSym=4096, MaxProc=2048) plus an
  open-addressing hash for FindSym/FindProc (HashSize=8192): the
  compiler session lowers in ~1.5s.  The old "hash miscompile" was a
  symptom of the miscompiled Lower and is gone.
- CopySpan takes its source as a VAR open array: gm2 copies a value
  open-array formal to a stack VLA of its full length, which overflowed
  the stack for the multi-megabyte tables.
- External procs recorded via MarkProcExternal (no uid, no static link);
  indirect calls through proc-typed variables via CallBeginInd.
- Grammar AST fixes: ActParam preserves the enclosing call's argument
  list (Factor used to reset astNArgs for designator actuals); ImplUnit/
  ProgModule clear the stale astStmt so a module without BEGIN does not
  grow a spurious _init.
- Const values live in Lower's per-unit tables (every module has EOL,
  so SymTab collided across modules after EndUnit); LEN/HIGH added.

The compiler session lowers but is not yet byte-identical: M2S.Get
($Get_61) is missing a large CASE arm.  Suite 204/204; FIXPOINT OK
(3,695,508 bytes), session 0 errors.
Eric Streit há 6 dias atrás
pai
commit
4cc3a0e293

+ 6 - 0
compiler/src/Lower.def

@@ -17,6 +17,10 @@ PROCEDURE CanLower (unit: AST.Node): BOOLEAN;
 PROCEDURE LowerUnit (unit: AST.Node);
 PROCEDURE LowerUnit (unit: AST.Node);
 PROCEDURE LowerSession (units: ARRAY OF AST.Node; n: CARDINAL;
 PROCEDURE LowerSession (units: ARRAY OF AST.Node; n: CARDINAL;
                         progName: ARRAY OF CHAR);
                         progName: ARRAY OF CHAR);
+PROCEDURE BeginSession (progName: ARRAY OF CHAR);
+PROCEDURE BeginUnit;
+PROCEDURE LowerUnitNow (unit: AST.Node; isProg: BOOLEAN);
+PROCEDURE EndSession;
 
 
 (* The expression/statement/declaration walkers are mutually recursive.
 (* The expression/statement/declaration walkers are mutually recursive.
    gm2's link pass rejects FORWARD and V3 has no whole-module pass, so
    gm2's link pass rejects FORWARD and V3 has no whole-module pass, so
@@ -33,10 +37,12 @@ PROCEDURE LowerDecls (seq: AST.Node);
 
 
 PROCEDURE Reset;
 PROCEDURE Reset;
 PROCEDURE NoteVar (name: ARRAY OF CHAR; kind: INTEGER; t: SymTab.TypeIndex);
 PROCEDURE NoteVar (name: ARRAY OF CHAR; kind: INTEGER; t: SymTab.TypeIndex);
+PROCEDURE NoteConstVal (val: ARRAY OF CHAR);
 PROCEDURE ScopeEnter;
 PROCEDURE ScopeEnter;
 PROCEDURE ScopeLeave;
 PROCEDURE ScopeLeave;
 PROCEDURE NoteProc (name: ARRAY OF CHAR; uid: CARDINAL;
 PROCEDURE NoteProc (name: ARRAY OF CHAR; uid: CARDINAL;
                     res: SymTab.TypeIndex; depth: CARDINAL; ext: BOOLEAN);
                     res: SymTab.TypeIndex; depth: CARDINAL; ext: BOOLEAN);
+PROCEDURE MarkProcExternal;
 PROCEDURE SetProcRes (t: SymTab.TypeIndex);
 PROCEDURE SetProcRes (t: SymTab.TypeIndex);
 PROCEDURE NoteParam (name: ARRAY OF CHAR; t: SymTab.TypeIndex;
 PROCEDURE NoteParam (name: ARRAY OF CHAR; t: SymTab.TypeIndex;
                      isVar: BOOLEAN);
                      isVar: BOOLEAN);

+ 212 - 47
compiler/src/Lower.mod

@@ -13,15 +13,17 @@ IMPORT AST, SymTab, QbeGen;
    Names live in flat CHAR arrays indexed manually at slot * 64. *)
    Names live in flat CHAR arrays indexed manually at slot * 64. *)
 
 
 CONST
 CONST
-  MaxSym  = 512;
+  MaxSym  = 4096;
   MaxName = 63;
   MaxName = 63;
-  MaxProc = 64;
+  MaxProc = 2048;   (* whole-session: the compiler declares >1200 procs *)
   MaxPar  = 32;
   MaxPar  = 32;
+  HashSize = 8192;  (* power of two > MaxSym, MaxProc: open addressing *)
 
 
 VAR
 VAR
   symName  : ARRAY [0 .. MaxSym * 64 - 1] OF CHAR;
   symName  : ARRAY [0 .. MaxSym * 64 - 1] OF CHAR;
   symKind  : ARRAY [0 .. MaxSym - 1] OF INTEGER;
   symKind  : ARRAY [0 .. MaxSym - 1] OF INTEGER;
   symTy    : ARRAY [0 .. MaxSym - 1] OF SymTab.TypeIndex;
   symTy    : ARRAY [0 .. MaxSym - 1] OF SymTab.TypeIndex;
+  symVal   : ARRAY [0 .. MaxSym - 1] OF QbeGen.QVal;  (* const value *)
   nSyms    : CARDINAL;
   nSyms    : CARDINAL;
   procName : ARRAY [0 .. MaxProc * 64 - 1] OF CHAR;
   procName : ARRAY [0 .. MaxProc * 64 - 1] OF CHAR;
   procUid  : ARRAY [0 .. MaxProc - 1] OF CARDINAL;
   procUid  : ARRAY [0 .. MaxProc - 1] OF CARDINAL;
@@ -33,6 +35,11 @@ VAR
   parVar   : ARRAY [0 .. MaxProc * MaxPar - 1] OF BOOLEAN;
   parVar   : ARRAY [0 .. MaxProc * MaxPar - 1] OF BOOLEAN;
   parName  : ARRAY [0 .. MaxProc * MaxPar * 64 - 1] OF CHAR;
   parName  : ARRAY [0 .. MaxProc * MaxPar * 64 - 1] OF CHAR;
   nProcs   : CARDINAL;
   nProcs   : CARDINAL;
+  (* Open-addressing indexes into symName/procName: entry = index+1,
+     0 = empty.  A whole-session linear scan is too slow (thousands of
+     lookups over thousands of entries), so FindSym/FindProc hash. *)
+  symHash  : ARRAY [0 .. HashSize - 1] OF INTEGER;
+  procHash : ARRAY [0 .. HashSize - 1] OF INTEGER;
   withRec  : ARRAY [0 .. 15] OF SymTab.TypeIndex;
   withRec  : ARRAY [0 .. 15] OF SymTab.TypeIndex;
   withTop  : CARDINAL;
   withTop  : CARDINAL;
   curMethCls : SymTab.TypeIndex;   (* class bound by the last obj.Method *)
   curMethCls : SymTab.TypeIndex;   (* class bound by the last obj.Method *)
@@ -47,8 +54,11 @@ PROCEDURE CopyN (src: ARRAY OF CHAR; VAR dst: ARRAY OF CHAR);
     dst[i] := CHR(0)
     dst[i] := CHR(0)
   END CopyN;
   END CopyN;
 
 
-PROCEDURE CopySpan (src: ARRAY OF CHAR; VAR dst: ARRAY OF CHAR;
+PROCEDURE CopySpan (VAR src: ARRAY OF CHAR; VAR dst: ARRAY OF CHAR;
                     srcOff, dstOff: CARDINAL);
                     srcOff, dstOff: CARDINAL);
+(* src is a VAR open array on purpose: gm2 copies a value open-array
+   formal to a stack VLA of its full length, which overflows the stack
+   for the multi-megabyte tables below. *)
   VAR i: CARDINAL;
   VAR i: CARDINAL;
   BEGIN
   BEGIN
     i := 0;
     i := 0;
@@ -58,18 +68,83 @@ PROCEDURE CopySpan (src: ARRAY OF CHAR; VAR dst: ARRAY OF CHAR;
     dst[dstOff + i] := CHR(0)
     dst[dstOff + i] := CHR(0)
   END CopySpan;
   END CopySpan;
 
 
+PROCEDURE HashStr (name: ARRAY OF CHAR): CARDINAL;
+  VAR h: CARDINAL; i: CARDINAL;
+  BEGIN
+    h := 5381; i := 0;
+    WHILE (i <= HIGH(name)) AND (name[i] # CHR(0)) DO
+      h := (h * 33 + VAL(CARDINAL, ORD(name[i]))) MOD HashSize; INC(i)
+    END;
+    RETURN h
+  END HashStr;
+
+PROCEDURE SpanEq (VAR arr: ARRAY OF CHAR; off: CARDINAL;
+                  name: ARRAY OF CHAR): BOOLEAN;
+  VAR j: CARDINAL;
+  BEGIN
+    j := 0;
+    WHILE (j < MaxName) AND (name[j] # CHR(0)) DO
+      IF arr[off + j] # name[j] THEN RETURN FALSE END;
+      INC(j)
+    END;
+    RETURN arr[off + j] = name[j]
+  END SpanEq;
+
+PROCEDURE HashPut (VAR htab: ARRAY OF INTEGER; VAR names: ARRAY OF CHAR;
+                   name: ARRAY OF CHAR; idx: CARDINAL);
+(* Insert idx, replacing an existing entry with the same name so the
+   latest declaration wins (the linear scan used to search backward). *)
+  VAR h: CARDINAL;
+  BEGIN
+    h := HashStr(name);
+    LOOP
+      IF htab[h] = 0 THEN htab[h] := VAL(INTEGER, idx) + 1; RETURN END;
+      IF SpanEq(names, VAL(CARDINAL, htab[h] - 1) * 64, name) THEN
+        htab[h] := VAL(INTEGER, idx) + 1; RETURN
+      END;
+      h := (h + 1) MOD HashSize
+    END
+  END HashPut;
+
+PROCEDURE HashGet (VAR htab: ARRAY OF INTEGER; VAR names: ARRAY OF CHAR;
+                   name: ARRAY OF CHAR): INTEGER;
+  VAR h: CARDINAL;
+  BEGIN
+    h := HashStr(name);
+    LOOP
+      IF htab[h] = 0 THEN RETURN -1 END;
+      IF SpanEq(names, VAL(CARDINAL, htab[h] - 1) * 64, name) THEN
+        RETURN htab[h] - 1
+      END;
+      h := (h + 1) MOD HashSize
+    END
+  END HashGet;
+
 PROCEDURE Reset;
 PROCEDURE Reset;
+  VAR i: CARDINAL;
   BEGIN
   BEGIN
-    nSyms := 0; nProcs := 0
+    nSyms := 0; nProcs := 0;
+    i := 0;
+    WHILE i < HashSize DO symHash[i] := 0; procHash[i] := 0; INC(i) END
   END Reset;
   END Reset;
 
 
 PROCEDURE NoteVar (name: ARRAY OF CHAR; kind: INTEGER; t: SymTab.TypeIndex);
 PROCEDURE NoteVar (name: ARRAY OF CHAR; kind: INTEGER; t: SymTab.TypeIndex);
   BEGIN
   BEGIN
     IF nSyms >= MaxSym THEN RETURN END;
     IF nSyms >= MaxSym THEN RETURN END;
     CopySpan(name, symName, 0, nSyms * 64);
     CopySpan(name, symName, 0, nSyms * 64);
-    symKind[nSyms] := kind; symTy[nSyms] := t; INC(nSyms)
+    symKind[nSyms] := kind; symTy[nSyms] := t;
+    HashPut(symHash, symName, name, nSyms);
+    INC(nSyms)
   END NoteVar;
   END NoteVar;
 
 
+PROCEDURE NoteConstVal (val: ARRAY OF CHAR);
+(* Records the evaluated value of the CONST just entered by NoteVar.
+   Const values are per-unit and cross-module names collide (every
+   module has EOL), so they live here, not in SymTab. *)
+  BEGIN
+    IF nSyms > 0 THEN CopyN(val, symVal[nSyms - 1]) END
+  END NoteConstVal;
+
 PROCEDURE ScopeEnter;
 PROCEDURE ScopeEnter;
   BEGIN
   BEGIN
   END ScopeEnter;
   END ScopeEnter;
@@ -86,9 +161,17 @@ PROCEDURE NoteProc (name: ARRAY OF CHAR; uid: CARDINAL;
     procUid[nProcs] := uid; procRes[nProcs] := res;
     procUid[nProcs] := uid; procRes[nProcs] := res;
     procDep[nProcs] := depth; procExt[nProcs] := ext;
     procDep[nProcs] := depth; procExt[nProcs] := ext;
     procNpar[nProcs] := 0;
     procNpar[nProcs] := 0;
+    HashPut(procHash, procName, name, nProcs);
     INC(nProcs)
     INC(nProcs)
   END NoteProc;
   END NoteProc;
 
 
+PROCEDURE MarkProcExternal;
+(* The grammar calls this from the EXTERNAL branch, after NoteProc, so
+   the walk records that the callee takes no static link. *)
+  BEGIN
+    IF nProcs > 0 THEN procExt[nProcs - 1] := TRUE END
+  END MarkProcExternal;
+
 PROCEDURE SetProcRes (t: SymTab.TypeIndex);
 PROCEDURE SetProcRes (t: SymTab.TypeIndex);
   BEGIN
   BEGIN
     IF nProcs > 0 THEN procRes[nProcs - 1] := t END
     IF nProcs > 0 THEN procRes[nProcs - 1] := t END
@@ -108,37 +191,25 @@ PROCEDURE NoteParam (name: ARRAY OF CHAR; t: SymTab.TypeIndex; isVar: BOOLEAN);
 
 
 PROCEDURE FindSym (name: ARRAY OF CHAR; VAR kind: INTEGER;
 PROCEDURE FindSym (name: ARRAY OF CHAR; VAR kind: INTEGER;
                    VAR t: SymTab.TypeIndex): BOOLEAN;
                    VAR t: SymTab.TypeIndex): BOOLEAN;
-  VAR i: CARDINAL; j: CARDINAL; ok: BOOLEAN;
-  BEGIN
-    i := nSyms;
-    WHILE i > 0 DO
-      DEC(i);
-      ok := TRUE; j := 0;
-      WHILE ok AND (j < 64) AND (symName[i * 64 + j] # CHR(0)) DO
-        IF symName[i * 64 + j] # name[j] THEN ok := FALSE END;
-        INC(j)
-      END;
-      IF ok AND (symName[i * 64 + j] = name[j]) THEN
-        kind := symKind[i]; t := symTy[i]; RETURN TRUE
-      END
-    END;
-    RETURN FALSE
+  VAR i: INTEGER;
+  BEGIN
+    i := HashGet(symHash, symName, name);
+    IF i < 0 THEN RETURN FALSE END;
+    kind := symKind[i]; t := symTy[i]; RETURN TRUE
   END FindSym;
   END FindSym;
 
 
+PROCEDURE GetConstVal (name: ARRAY OF CHAR; VAR v: ARRAY OF CHAR): BOOLEAN;
+  VAR i: INTEGER;
+  BEGIN
+    i := HashGet(symHash, symName, name);
+    IF (i < 0) OR (symKind[i] # SymTab.KindConst) THEN RETURN FALSE END;
+    CopyN(symVal[i], v);
+    RETURN v[0] # CHR(0)
+  END GetConstVal;
+
 PROCEDURE FindProc (name: ARRAY OF CHAR): INTEGER;
 PROCEDURE FindProc (name: ARRAY OF CHAR): INTEGER;
-  VAR i: CARDINAL; j: CARDINAL; ok: BOOLEAN;
-  BEGIN
-    i := nProcs;
-    WHILE i > 0 DO
-      DEC(i);
-      ok := TRUE; j := 0;
-      WHILE ok AND (j < 64) AND (procName[i * 64 + j] # CHR(0)) DO
-        IF procName[i * 64 + j] # name[j] THEN ok := FALSE END;
-        INC(j)
-      END;
-      IF ok AND (procName[i * 64 + j] = name[j]) THEN RETURN VAL(INTEGER, i) END
-    END;
-    RETURN -1
+  BEGIN
+    RETURN HashGet(procHash, procName, name)
   END FindProc;
   END FindProc;
 
 
 PROCEDURE Resolve (nm: ARRAY OF CHAR; VAR k: INTEGER;
 PROCEDURE Resolve (nm: ARRAY OF CHAR; VAR k: INTEGER;
@@ -158,9 +229,10 @@ PROCEDURE ProcInfo (nm: ARRAY OF CHAR; VAR uid: CARDINAL;
   VAR i: INTEGER;
   VAR i: INTEGER;
   BEGIN
   BEGIN
     i := FindProc(nm);
     i := FindProc(nm);
-    IF i < 0 THEN RETURN FALSE END;
+    IF (i < 0) OR (i >= MaxProc) THEN RETURN FALSE END;
     uid := procUid[i]; res := procRes[i]; depth := procDep[i];
     uid := procUid[i]; res := procRes[i]; depth := procDep[i];
     ext := procExt[i]; npar := procNpar[i];
     ext := procExt[i]; npar := procNpar[i];
+    IF npar > MaxPar THEN npar := MaxPar END;
     RETURN TRUE
     RETURN TRUE
   END ProcInfo;
   END ProcInfo;
 
 
@@ -170,7 +242,8 @@ PROCEDURE ParamInfo (nm: ARRAY OF CHAR; i: CARDINAL;
   VAR k: INTEGER;
   VAR k: INTEGER;
   BEGIN
   BEGIN
     k := FindProc(nm);
     k := FindProc(nm);
-    IF (k < 0) OR (i >= procNpar[k]) THEN RETURN FALSE END;
+    IF (k < 0) OR (k >= MaxProc) THEN RETURN FALSE END;
+    IF (i >= procNpar[k]) OR (procNpar[k] > MaxPar) THEN RETURN FALSE END;
     t := parTy[VAL(CARDINAL, k) * MaxPar + i];
     t := parTy[VAL(CARDINAL, k) * MaxPar + i];
     isVar := parVar[VAL(CARDINAL, k) * MaxPar + i];
     isVar := parVar[VAL(CARDINAL, k) * MaxPar + i];
     CopySpan(parName, pn, VAL(CARDINAL, k) * MaxPar * 64 + i * 64, 0);
     CopySpan(parName, pn, VAL(CARDINAL, k) * MaxPar * 64 + i * 64, 0);
@@ -831,7 +904,7 @@ PROCEDURE LowerDesign (n: AST.Node; VAR t: SymTab.TypeIndex; VAR k: INTEGER;
         t := SymTab.BoolType(); QbeGen.CopyOp("0", q)
         t := SymTab.BoolType(); QbeGen.CopyOp("0", q)
       ELSIF SymTab.Equal(nm, "NIL") THEN
       ELSIF SymTab.Equal(nm, "NIL") THEN
         QbeGen.CopyOp("0", q)
         QbeGen.CopyOp("0", q)
-      ELSIF SymTab.GetSymVal(nm, cv) AND QbeGen.IsImm(cv) THEN
+      ELSIF GetConstVal(nm, cv) AND QbeGen.IsImm(cv) THEN
         QbeGen.CopyOp(cv, q)
         QbeGen.CopyOp(cv, q)
       ELSE QbeGen.CopyOp("0", q)
       ELSE QbeGen.CopyOp("0", q)
       END
       END
@@ -925,25 +998,52 @@ PROCEDURE LowerDesign (n: AST.Node; VAR t: SymTab.TypeIndex; VAR k: INTEGER;
 
 
 PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
 PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
                      VAR q: QbeGen.QVal; want: BOOLEAN);
                      VAR q: QbeGen.QVal; want: BOOLEAN);
-  VAR nm, pn: SymTab.Name; i: CARDINAL; at, ft, res: SymTab.TypeIndex;
-      qa, qt, qe, mg: QbeGen.QVal; conv, isV, qual: BOOLEAN;
-      uid, depth, np: CARDINAL; ext: BOOLEAN;
+  VAR nm, pn, cqn: SymTab.Name; i: CARDINAL; at, ft, res: SymTab.TypeIndex;
+      qa, qt, qe, mg: QbeGen.QVal; conv, isV, qual, ind: BOOLEAN;
+      uid, depth, np: CARDINAL; ext: BOOLEAN; ck: INTEGER;
+      ct: SymTab.TypeIndex; csfx: BOOLEAN;
   BEGIN
   BEGIN
     qual := CalleeAlias(n, nm);
     qual := CalleeAlias(n, nm);
+    ind := FALSE;
     IF qual THEN
     IF qual THEN
       uid := SymTab.ProcUid(nm); res := SymTab.ProcRes(nm);
       uid := SymTab.ProcUid(nm); res := SymTab.ProcRes(nm);
       depth := SymTab.ProcDepthOf(nm); ext := SymTab.IsExternal(nm);
       depth := SymTab.ProcDepthOf(nm); ext := SymTab.IsExternal(nm);
-      np := SymTab.ProcNPar(nm)
+      np := SymTab.ProcNPar(nm);
+      IF ext THEN QbeGen.CopyOp(nm, mg) ELSE QbeGen.Mangled(nm, uid, mg) END;
+      QbeGen.CallBegin(mg, res, depth, ext)
     ELSE
     ELSE
       CalleeName(n, nm);
       CalleeName(n, nm);
-      IF NOT ProcInfo(nm, uid, res, depth, ext, np) THEN END
+      IF FindSym(nm, ck, ct)
+         AND ((ck = SymTab.KindVar) OR (ck = SymTab.KindParam))
+         AND (SymTab.ClassOf(ct) = SymTab.ClProc) THEN
+        (* indirect call through a procedure-typed variable *)
+        ind := TRUE;
+        LowerDesign(AST.Child(n, 0), ct, ck, qa, cqn, csfx);
+        res := SymTab.ProcTypeRes(ct);
+        np := SymTab.ProcTypeNPar(ct);
+        QbeGen.CallBeginInd(qa, res, FALSE)
+      ELSIF ProcInfo(nm, uid, res, depth, ext, np) THEN
+        IF ext THEN
+          (* External callee: bind to its C link name (the procedure's
+             own name); SymTab.ProcLink cannot be consulted after
+             EndUnit. *)
+          QbeGen.CopyOp(nm, mg)
+        ELSE
+          QbeGen.Mangled(nm, uid, mg)
+        END;
+        QbeGen.CallBegin(mg, res, depth, ext)
+      ELSE
+        res := SymTab.InvalidType;
+        QbeGen.CallBegin(nm, res, 0, FALSE)
+      END
     END;
     END;
-    QbeGen.Mangled(nm, uid, mg);
-    QbeGen.CallBegin(mg, res, depth, ext);
     i := 1;
     i := 1;
     WHILE i < AST.NChild(n) DO
     WHILE i < AST.NChild(n) DO
       LowerExpr(AST.Child(n, i), at, qe);
       LowerExpr(AST.Child(n, i), at, qe);
-      IF qual THEN
+      IF ind THEN
+        ft := SymTab.ProcTypeParamType(ct, i - 1);
+        isV := SymTab.ProcTypeParamIsVar(ct, i - 1)
+      ELSIF qual THEN
         ft := SymTab.ParamType(nm, i - 1);
         ft := SymTab.ParamType(nm, i - 1);
         isV := SymTab.ParamIsVar(nm, i - 1)
         isV := SymTab.ParamIsVar(nm, i - 1)
       ELSE
       ELSE
@@ -977,6 +1077,7 @@ PROCEDURE IsBuiltin (nm: ARRAY OF CHAR): BOOLEAN;
     RETURN SymTab.Equal(nm, "ABS") OR SymTab.Equal(nm, "CAP")
     RETURN SymTab.Equal(nm, "ABS") OR SymTab.Equal(nm, "CAP")
         OR SymTab.Equal(nm, "CHR") OR SymTab.Equal(nm, "ORD")
         OR SymTab.Equal(nm, "CHR") OR SymTab.Equal(nm, "ORD")
         OR SymTab.Equal(nm, "SIZE") OR SymTab.Equal(nm, "TSIZE")
         OR SymTab.Equal(nm, "SIZE") OR SymTab.Equal(nm, "TSIZE")
+        OR SymTab.Equal(nm, "LEN") OR SymTab.Equal(nm, "HIGH")
         OR SymTab.Equal(nm, "MIN") OR SymTab.Equal(nm, "MAX")
         OR SymTab.Equal(nm, "MIN") OR SymTab.Equal(nm, "MAX")
         OR SymTab.Equal(nm, "SHIFT") OR SymTab.Equal(nm, "ROTATE")
         OR SymTab.Equal(nm, "SHIFT") OR SymTab.Equal(nm, "ROTATE")
         OR SymTab.Equal(nm, "ADR")
         OR SymTab.Equal(nm, "ADR")
@@ -1022,6 +1123,22 @@ PROCEDURE LowerBuiltinExpr (n: AST.Node; nm: SymTab.Name;
         t := SymTab.IntType()
         t := SymTab.IntType()
       ELSE t := SymTab.InvalidType; QbeGen.CopyOp("0", q)
       ELSE t := SymTab.InvalidType; QbeGen.CopyOp("0", q)
       END
       END
+    ELSIF SymTab.Equal(nm, "LEN") OR SymTab.Equal(nm, "HIGH") THEN
+      LowerDesign(AST.Child(n, 1), dt, k, qd, qn, sfx);
+      IF dt = SymTab.InvalidType THEN
+        t := SymTab.InvalidType; QbeGen.CopyOp("0", q)
+      ELSIF SymTab.ClassOf(dt) = SymTab.ClArray THEN
+        IF SymTab.Equal(nm, "HIGH") THEN
+          IF SymTab.IsOpenArray(dt) THEN QbeGen.OpenHi(qd, q)
+          ELSE QbeGen.IntStr(SymTab.ArrayHi(dt), q) END
+        ELSE
+          IF SymTab.IsOpenArray(dt) THEN QbeGen.LoadCount(qd, q)
+          ELSE QbeGen.IntStr(VAL(INTEGER, SymTab.ArrayLen(dt)), q) END
+        END;
+        t := SymTab.IntType()
+      ELSE
+        t := SymTab.InvalidType; QbeGen.CopyOp("0", q)
+      END
     ELSIF SymTab.Equal(nm, "SHIFT") OR SymTab.Equal(nm, "ROTATE") THEN
     ELSIF SymTab.Equal(nm, "SHIFT") OR SymTab.Equal(nm, "ROTATE") THEN
       LowerExpr(AST.Child(n, 1), et, q);
       LowerExpr(AST.Child(n, 1), et, q);
       LowerExpr(AST.Child(n, 2), et2, q2);
       LowerExpr(AST.Child(n, 2), et2, q2);
@@ -1760,9 +1877,13 @@ PROCEDURE LowerDecls (seq: AST.Node);
         END
         END
       ELSIF AST.Kind(c) = AST.NkConstDecl THEN
       ELSIF AST.Kind(c) = AST.NkConstDecl THEN
         Name(AST.Child(c, 0), nm);
         Name(AST.Child(c, 0), nm);
-        Name(AST.Child(c, 1), txt);
-        QbeGen.NormInt(txt, qv);
-        QbeGen.DeclConst(nm, qv, AST.Ty(c))
+        (* The value is a constant expression (CHR(..), arithmetic, a
+           prior CONST): use the evaluated value recorded by NoteConstVal,
+           not the AST text.  Aggregates/strings store a descriptor
+           address and are not scalar data. *)
+        IF GetConstVal(nm, qv) AND QbeGen.IsImm(qv) THEN
+          QbeGen.DeclConst(nm, qv, AST.Ty(c))
+        END
       ELSIF AST.Kind(c) = AST.NkProcDecl THEN
       ELSIF AST.Kind(c) = AST.NkProcDecl THEN
         Name(AST.Child(c, 0), nm);
         Name(AST.Child(c, 0), nm);
         IF ProcInfo(nm, uid, res, depth, ext, np) THEN
         IF ProcInfo(nm, uid, res, depth, ext, np) THEN
@@ -1863,6 +1984,50 @@ PROCEDURE LowerSession (units: ARRAY OF AST.Node; n: CARDINAL;
     QbeGen.EndModule(pn)
     QbeGen.EndModule(pn)
   END LowerSession;
   END LowerSession;
 
 
+PROCEDURE BeginSession (progName: ARRAY OF CHAR);
+(* Per-unit Lower emission.  The grammar still runs its inline emit while
+   parsing, but under NoEmit it only mutates QbeGen's counters (nothing
+   reaches the buffer).  For each unit: BeginUnit snapshots the state
+   *before* parsing; after parsing, LowerUnitNow restores that snapshot
+   and lets Lower re-emit the unit from the same counter values the
+   legacy inline path would have used, so the image stays byte-identical.
+   EndSession writes the accumulated session image. *)
+  BEGIN
+    withTop := 0;
+    curImplCls := SymTab.InvalidType;
+    QbeGen.OpenModule(progName);
+    QbeGen.SetNoEmit(TRUE)
+  END BeginSession;
+
+PROCEDURE BeginUnit;
+(* Snapshot the pre-parse state so Lower can discard the grammar's
+   suppressed inline mutations for this unit. *)
+  BEGIN
+    QbeGen.SaveSession
+  END BeginUnit;
+
+PROCEDURE LowerUnitNow (unit: AST.Node; isProg: BOOLEAN);
+  BEGIN
+    IF unit = AST.NoNode THEN RETURN END;
+    QbeGen.RestoreSession;
+    QbeGen.SetNoEmit(FALSE);
+    LowerOneUnit(unit, isProg);
+    QbeGen.SetNoEmit(TRUE)
+  END LowerUnitNow;
+
+PROCEDURE EndSession;
+  VAR out: ARRAY [0 .. 255] OF CHAR; j: CARDINAL;
+  BEGIN
+    j := 0;
+    WHILE (j <= 254) AND (curUnitName[j] # CHR(0)) DO
+      out[j] := curUnitName[j]; INC(j)
+    END;
+    out[j] := "L"; INC(j); out[j] := CHR(0);
+    QbeGen.SetNoEmit(FALSE);
+    QbeGen.SetOutName(out);
+    QbeGen.EndModule(curUnitName)
+  END EndSession;
+
 PROCEDURE LowerUnit (unit: AST.Node);
 PROCEDURE LowerUnit (unit: AST.Node);
   VAR nm: SymTab.Name; out: ARRAY [0 .. 255] OF CHAR; i: CARDINAL;
   VAR nm: SymTab.Name; out: ARRAY [0 .. 255] OF CHAR; i: CARDINAL;
   BEGIN
   BEGIN

+ 24 - 5
compiler/src/M2.atg

@@ -318,7 +318,7 @@ PRODUCTIONS
       ";"
       ";"
       { Import                        (. AstAppend(AST.NkDeclSeq,
       { Import                        (. AstAppend(AST.NkDeclSeq,
                                              astImp, astImpTail, astDecl); .) }
                                              astImp, astImpTail, astDecl); .) }
-      DeclSeq
+      DeclSeq                           (. astStmt := AST.NoNode; .)
       [ "BEGIN"                         (. FlushPend;
       [ "BEGIN"                         (. FlushPend;
                                            k := 0;
                                            k := 0;
                                            WHILE k < SymTab.FwdPending() DO
                                            WHILE k < SymTab.FwdPending() DO
@@ -368,7 +368,7 @@ PRODUCTIONS
       ";"
       ";"
       { Import                        (. AstAppend(AST.NkDeclSeq,
       { Import                        (. AstAppend(AST.NkDeclSeq,
                                              astImp, astImpTail, astDecl); .) }
                                              astImp, astImpTail, astDecl); .) }
-      DeclSeq
+      DeclSeq                           (. astStmt := AST.NoNode; .)
       [ "BEGIN"                         (. FlushPend;
       [ "BEGIN"                         (. FlushPend;
                                            k := 0;
                                            k := 0;
                                            WHILE k < SymTab.FwdPending() DO
                                            WHILE k < SymTab.FwdPending() DO
@@ -441,7 +441,7 @@ PRODUCTIONS
         { "," GetIdent<expNames[expCount]>
         { "," GetIdent<expNames[expCount]>
                                         (. INC(expCount); .) }
                                         (. INC(expCount); .) }
         ";" ]
         ";" ]
-      DeclSeq
+      DeclSeq                           (. astStmt := AST.NoNode; .)
       [ "BEGIN"                         (. astStmt := AST.NoNode;
       [ "BEGIN"                         (. astStmt := AST.NoNode;
                                            QbeGen.BeginInit(m1); .)
                                            QbeGen.BeginInit(m1); .)
         [ StatSeq ]                     (. QbeGen.EndInit; .) ]
         [ StatSeq ]                     (. QbeGen.EndInit; .) ]
@@ -1032,6 +1032,7 @@ PRODUCTIONS
                                            AST.SetTy(astNode, t);
                                            AST.SetTy(astNode, t);
                                            astDecl := astNode;
                                            astDecl := astNode;
                                            Lower.NoteVar(n, SymTab.KindConst, t);
                                            Lower.NoteVar(n, SymTab.KindConst, t);
+                                           Lower.NoteConstVal(qv);
                                            SymTab.SetSymType(n, t);
                                            SymTab.SetSymType(n, t);
                                            cls := SymTab.ClassOf(t);
                                            cls := SymTab.ClassOf(t);
                                            IF (cls = SymTab.ClArray)
                                            IF (cls = SymTab.ClArray)
@@ -1247,6 +1248,7 @@ PRODUCTIONS
                                            QbeGen.AbortFunc; .)
                                            QbeGen.AbortFunc; .)
       | "EXTERNAL"                      (. AST.SetOp(astNode, 2);
       | "EXTERNAL"                      (. AST.SetOp(astNode, 2);
                                            astDecl := astNode;
                                            astDecl := astNode;
+                                           Lower.MarkProcExternal;
                                            Lower.ScopeLeave;
                                            Lower.ScopeLeave;
                                            SymTab.MarkExternal("");
                                            SymTab.MarkExternal("");
                                            SymTab.CloseProc;
                                            SymTab.CloseProc;
@@ -1760,8 +1762,25 @@ PRODUCTIONS
                                         (. VAR at, ft: SymTab.TypeIndex;
                                         (. VAR at, ft: SymTab.TypeIndex;
                                              qe, qa, qt: QbeGen.QVal;
                                              qe, qa, qt: QbeGen.QVal;
                                              isV, conv, va: BOOLEAN;
                                              isV, conv, va: BOOLEAN;
-                                             cl: CHAR; .)
-    = Expr<at, qe> (. IF astNArgs <= HIGH(astArgs) THEN
+                                             cl: CHAR;
+                                             savedN, aj: CARDINAL;
+                                             savedArgs: ARRAY [0 .. 31]
+                                               OF AST.Node; .)
+    =                                   (. (* Parsing the actual can clobber
+                                             the enclosing call's argument
+                                             list (Factor resets astNArgs),
+                                             so save/restore it. *)
+                                           savedN := astNArgs; aj := 0;
+                                           WHILE aj <= HIGH(astArgs) DO
+                                             savedArgs[aj] := astArgs[aj];
+                                             INC(aj)
+                                           END; .)
+      Expr<at, qe>                      (. astNArgs := savedN; aj := 0;
+                                           WHILE aj <= HIGH(astArgs) DO
+                                             astArgs[aj] := savedArgs[aj];
+                                             INC(aj)
+                                           END;
+                                           IF astNArgs <= HIGH(astArgs) THEN
                                              astArgs[astNArgs] := astCur;
                                              astArgs[astNArgs] := astCur;
                                              INC(astNArgs)
                                              INC(astNArgs)
                                            END;
                                            END;

Diff do ficheiro suprimidas por serem muito extensas
+ 2806 - 2787
compiler/src/M2.lst


+ 4 - 0
compiler/src/QbeGen.def

@@ -62,6 +62,10 @@ PROCEDURE SetNoEmit (b: BOOLEAN);
 (* Suppresses all output while TRUE (nested procedures in 4.1:
 (* Suppresses all output while TRUE (nested procedures in 4.1:
    SymTab tracks, QbeGen stays silent). *)
    SymTab tracks, QbeGen stays silent). *)
 
 
+PROCEDURE SaveSession;
+PROCEDURE RestoreSession;
+(* Snapshot/restore the session counters (Lower per-unit emit). *)
+
 PROCEDURE SetModule (name: ARRAY OF CHAR);
 PROCEDURE SetModule (name: ARRAY OF CHAR);
 (* Names the module being compiled; its globals emit as
 (* Names the module being compiled; its globals emit as
    "<mod>_<name>" so separate modules never collide. *)
    "<mod>_<name>" so separate modules never collide. *)

+ 42 - 2
compiler/src/QbeGen.mod

@@ -108,6 +108,44 @@ VAR
   fwdSite : ARRAY [0 .. 255] OF SymTab.Name;  (* real name to patch in *)
   fwdSite : ARRAY [0 .. 255] OF SymTab.Name;  (* real name to patch in *)
   fwdOper : ARRAY [0 .. 255] OF QVal;         (* final load/store class *)
   fwdOper : ARRAY [0 .. 255] OF QVal;         (* final load/store class *)
   fwdGlob : ARRAY [0 .. 255] OF BOOLEAN;
   fwdGlob : ARRAY [0 .. 255] OF BOOLEAN;
+  (* Session-state snapshot: Lower runs per unit while the grammar's
+     suppressed inline emit still mutates these, so Lower saves/restores
+     its own values around each unit. *)
+  sav_nTemp, sav_nLab, sav_loopTop, sav_nR, sav_nStr, sav_nLoc,
+  sav_nPar, sav_nArg, sav_nn, sav_nInit, sav_sessUsed, sav_delayTop,
+  sav_ustrN, sav_ustrUsed, sav_ctorN, sav_ctorTop, sav_funcDepth,
+  sav_scopeTop, sav_callDepth, sav_outSel, sav_withTop : CARDINAL;
+  sav_inBody, sav_inFunc, sav_useStack, sav_recvArmed : BOOLEAN;
+
+PROCEDURE SaveSession;
+  BEGIN
+    sav_nTemp := nTemp; sav_nLab := nLab; sav_loopTop := loopTop;
+    sav_nR := nR; sav_nStr := nStr; sav_nLoc := nLoc; sav_nPar := nPar;
+    sav_nArg := nArg; sav_nn := nn; sav_nInit := nInit;
+    sav_sessUsed := sessUsed; sav_delayTop := delayTop;
+    sav_ustrN := ustrN; sav_ustrUsed := ustrUsed;
+    sav_ctorN := ctorN; sav_ctorTop := ctorTop;
+    sav_funcDepth := funcDepth; sav_scopeTop := scopeTop;
+    sav_callDepth := callDepth; sav_outSel := outSel;
+    sav_withTop := withTop;
+    sav_inBody := inBody; sav_inFunc := inFunc;
+    sav_useStack := useStack; sav_recvArmed := recvArmed
+  END SaveSession;
+
+PROCEDURE RestoreSession;
+  BEGIN
+    nTemp := sav_nTemp; nLab := sav_nLab; loopTop := sav_loopTop;
+    nR := sav_nR; nStr := sav_nStr; nLoc := sav_nLoc; nPar := sav_nPar;
+    nArg := sav_nArg; nn := sav_nn; nInit := sav_nInit;
+    sessUsed := sav_sessUsed; delayTop := sav_delayTop;
+    ustrN := sav_ustrN; ustrUsed := sav_ustrUsed;
+    ctorN := sav_ctorN; ctorTop := sav_ctorTop;
+    funcDepth := sav_funcDepth; scopeTop := sav_scopeTop;
+    callDepth := sav_callDepth; outSel := sav_outSel;
+    withTop := sav_withTop;
+    inBody := sav_inBody; inFunc := sav_inFunc;
+    useStack := sav_useStack; recvArmed := sav_recvArmed
+  END RestoreSession;
 
 
 (* ---------------- small string utilities ---------------- *)
 (* ---------------- small string utilities ---------------- *)
 
 
@@ -890,6 +928,10 @@ PROCEDURE EndModule (name: ARRAY OF CHAR);
     ref : QVal;
     ref : QVal;
   BEGIN
   BEGIN
     IF NOT opened THEN RETURN END;
     IF NOT opened THEN RETURN END;
+    (* Per-unit Lower emission: the grammar's own unit-end EndModule must
+       not flush/close/write while the legacy emit is suppressed.  The
+       real session image is written by Lower.EndSession. *)
+    IF noEmit THEN RETURN END;
     IF NOT inBody THEN BeginBody END;
     IF NOT inBody THEN BeginBody END;
     IF SymTab.Lookup("ExitCode")
     IF SymTab.Lookup("ExitCode")
  AND (SymTab.SymKind("ExitCode") = SymTab.KindVar)
  AND (SymTab.SymKind("ExitCode") = SymTab.KindVar)
@@ -1000,7 +1042,6 @@ PROCEDURE LocAdd (name: ARRAY OF CHAR; tag: INTEGER; rep: ARRAY OF CHAR;
     off : QVal;
     off : QVal;
     lk : QVal;
     lk : QVal;
   BEGIN
   BEGIN
-    IF noEmit THEN RETURN END;
     IF (scopeTop = 0) OR (nLoc > HIGH(locNames)) THEN RETURN END;
     IF (scopeTop = 0) OR (nLoc > HIGH(locNames)) THEN RETURN END;
     idx := nLoc - scopeBase[scopeTop - 1];
     idx := nLoc - scopeBase[scopeTop - 1];
     IF idx > 63 THEN RETURN END;
     IF idx > 63 THEN RETURN END;
@@ -1167,7 +1208,6 @@ PROCEDURE FuncParam (name: ARRAY OF CHAR; isVar: BOOLEAN;
     tmp : QVal;
     tmp : QVal;
     buf : ARRAY [0 .. 71] OF CHAR;
     buf : ARRAY [0 .. 71] OF CHAR;
   BEGIN
   BEGIN
-    IF noEmit THEN RETURN TRUE END;
     IF nPar > HIGH(parCls) THEN RETURN FALSE END;
     IF nPar > HIGH(parCls) THEN RETURN FALSE END;
     IF isVar THEN c := "l" ELSE c := ResClass(t) END;
     IF isVar THEN c := "l" ELSE c := ResClass(t) END;
     parCls[nPar] := c;
     parCls[nPar] := c;

+ 6 - 11
compiler/src/compiler.frm

@@ -288,8 +288,6 @@ MODULE -->Grammar;
     f: CARDINAL;
     f: CARDINAL;
     bad: BOOLEAN;
     bad: BOOLEAN;
     lowerAst: BOOLEAN;
     lowerAst: BOOLEAN;
-    units: ARRAY [0 .. 31] OF AST.Node;
-    nUnits: CARDINAL;
 
 
   BEGIN
   BEGIN
     (* collect the session's source files (program module last) *)
     (* collect the session's source files (program module last) *)
@@ -313,12 +311,12 @@ MODULE -->Grammar;
     BaseName(files[nFiles - 1], progName);
     BaseName(files[nFiles - 1], progName);
     IF lowerAst THEN SetLower(TRUE) END;
     IF lowerAst THEN SetLower(TRUE) END;
     SymTab.Init;
     SymTab.Init;
-    IF lowerAst THEN AST.Init END;   (* once: -lower re-emits the session *)
     QbeGen.OpenModule(progName);
     QbeGen.OpenModule(progName);
+    IF lowerAst THEN Lower.BeginSession(progName) END;
     Error := StoreError;
     Error := StoreError;
 
 
     (* parse each unit into the shared session *)
     (* parse each unit into the shared session *)
-    bad := FALSE; f := 0; nUnits := 0;
+    bad := FALSE; f := 0;
     WHILE f < nFiles DO
     WHILE f < nFiles DO
       Strings.Assign(files[f], sourceName);
       Strings.Assign(files[f], sourceName);
       FileIO.Open(src, sourceName, FALSE);
       FileIO.Open(src, sourceName, FALSE);
@@ -334,11 +332,10 @@ MODULE -->Grammar;
 
 
       FileIO.WriteString(FileIO.StdOut, "Parsing");
       FileIO.WriteString(FileIO.StdOut, "Parsing");
       FileIO.WriteLn(FileIO.StdOut);
       FileIO.WriteLn(FileIO.StdOut);
-      IF NOT lowerAst THEN AST.Init END;
+      AST.Init;
+      IF lowerAst THEN Lower.BeginUnit END;
       Parse;
       Parse;
-      IF lowerAst AND (nUnits <= HIGH(units)) THEN
-        units[nUnits] := GetUnit(); INC(nUnits)
-      END;
+      IF lowerAst THEN Lower.LowerUnitNow(GetUnit(), f = nFiles - 1) END;
       PrintListing;
       PrintListing;
       IF lst # FileIO.StdOut THEN FileIO.Close(lst) END;
       IF lst # FileIO.StdOut THEN FileIO.Close(lst) END;
       IF NOT Successful() THEN
       IF NOT Successful() THEN
@@ -354,9 +351,7 @@ MODULE -->Grammar;
       INC(f)
       INC(f)
     END;
     END;
 
 
-    IF lowerAst AND (nUnits > 0) THEN
-      Lower.LowerSession(units, nUnits, progName)
-    END;
+    IF lowerAst THEN Lower.EndSession END;
 
 
     IF bad OR NOT SymTab.HaveProgram()
     IF bad OR NOT SymTab.HaveProgram()
       THEN FileIO.WriteString(FileIO.StdOut, "Incorrect source")
       THEN FileIO.WriteString(FileIO.StdOut, "Incorrect source")

+ 58 - 0
docs/summary_lower_l7.md

@@ -100,3 +100,61 @@ Reproduced with a **proc-only** hash (`HashName`/`ProcEq`/`HashPutProc`/
 
 
 Narrowing further needs many ~10-minute fixpoint cycles; reverted to
 Narrowing further needs many ~10-minute fixpoint cycles; reverted to
 green (`a9a8ecd`).  Suite 204/204.
 green (`a9a8ecd`).  Suite 204/204.
+
+---
+
+## Progress: per-unit flip unblocked (2026-10-05, after `a3ef897`)
+
+The `bad=T` that blocked per-unit emission was a **phantom**: it was the
+pre-existing `invalid call` errors in `Lower.mod` (forward references),
+now fixed by declaring the walkers in `Lower.def`.  With that gone, the
+per-unit flip works end to end.
+
+### Mechanism
+- Driver: `Lower.BeginSession` (open + `SetNoEmit`), `BeginUnit`
+  (`SaveSession`) before each `Parse`, `EndSession` (write image).
+- `Lower.LowerUnitNow(unit, isProg)` after each `Parse`:
+  `RestoreSession` → `SetNoEmit(FALSE)` → `LowerOneUnit` → `SetNoEmit(TRUE)`.
+- `QbeGen.EndModule` is a no-op under `NoEmit`, so the grammar's unit-end
+  emit neither flushes nor writes.
+- The grammar's suppressed inline emit must be a **faithful shadow** of
+  the legacy emit: `LocAdd`/`FuncParam` no longer skip `NewTemp` under
+  `NoEmit`, so the temp/`NoteAddr` numbering aligns with Lower's emit.
+
+### Name resolution
+- Whole-session tables: `MaxSym=4096`, `MaxProc=2048`, `MaxPar=32`.
+- Open-addressing hash index (`HashStr`/`SpanEq`/`HashPut`/`HashGet`,
+  `HashSize=8192`) for `FindSym`/`FindProc`.  The compiler session now
+  lowers in ~1.5 s.  (The earlier "hash miscompile" was a symptom of the
+  miscompiled `Lower`; it is gone now that `Lower` is V3-legal.)
+- `CopySpan`'s source is a **VAR** open array: gm2 copies a value
+  open-array formal to a stack VLA of its full length, which overflowed
+  the stack for the multi-megabyte tables.
+- External procs: `Lower.MarkProcExternal` (called from the grammar's
+  EXTERNAL branch) records `ext`; `LowerCall` binds external callees to
+  their own name (no uid, no static link).
+- Indirect calls through procedure-typed variables (`CurrentCh`):
+  `LowerCall` detects a proc-typed VAR/PARAM callee and uses
+  `CallBeginInd`.
+
+### Grammar AST fixes (also affect the two-phase AST generally)
+- `ActParam` saves/restores the enclosing call's `astArgs`/`astNArgs`:
+  `Factor` reset `astNArgs` for designator arguments, so any call with
+  two or more designator actuals kept only the last one.
+- `ImplUnit`/`ProgModule` reset `astStmt := AST.NoNode` after `DeclSeq`:
+  the module-body child was the stale last procedure body, so modules
+  without a module `BEGIN` (SysShim) grew a spurious `_init`.
+- `Lower.NoteConstVal`: const values are per-unit (every module has
+  `EOL`), so they live in Lower's tables, not `SymTab` (which collided
+  across modules after `EndUnit`).
+- `LEN`/`HIGH` added to `LowerBuiltinExpr`.
+
+### Remaining gap
+The compiler session now lowers, but is **not byte-identical** to the
+legacy image yet: the first divergence is in `M2S.Get` (`$Get_61`),
+where Lower emits 148 labels vs. the legacy 430 — a large part of the
+`Get` body (a big `CASE ch OF …`) is missing.  `lower_ok` single-file
+tests and the suite are green; the fixpoint is green (`-lower` is not
+used there).
+
+Suite **204/204**; FIXPOINT OK (3,695,508 bytes), session 0 errors.

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff