Browse Source

feat(symtab): nesting + record views, prefix 7/14 exact

Eric Streit 3 days ago
parent
commit
04df0c70b0
2 changed files with 225 additions and 138 deletions
  1. 166 138
      src/compiler/SYMTAB.MOD
  2. 59 0
      tools/score.py

+ 166 - 138
src/compiler/SYMTAB.MOD

@@ -33,7 +33,7 @@
 
 IMPLEMENTATION MODULE SymTab;
 IMPORT Compiler, Loader, Texts, Errors, Scanner, CodeGen; (* Files comes via DEF import (leak rule); re-listing is a hard error *)
-FROM SYSTEM IMPORT ADR, MOVE; (* ADDRESS/WORD come via DEF import (leak rule) *)
+FROM SYSTEM IMPORT ADR, MOVE, FILL; (* ADDRESS/WORD come via DEF import (leak rule); FILL is a real M-code builtin (042e) *)
 
 (* ---- heap model (groups B + C) ---- *)
 (* MCode words are untyped; CARDINAL()/T1()/ADDRESS() punning mirrors the
@@ -42,11 +42,18 @@ FROM SYSTEM IMPORT ADR, MOVE; (* ADDRESS/WORD come via DEF import (leak rule) *)
    kind <= 1, 10 for kind <= 6, 12 for kind <= 11, else 16). *)
 TYPE Words = POINTER TO ARRAY [0..7] OF CARDINAL;
 
+(* 10-byte module-name slot (group C names). Moved up: ownNamePtr uses it. *)
+TYPE ModuleSymPtr = POINTER TO ModuleSym;
+     ModuleSym = RECORD
+       name: ARRAY [0..7] OF CHAR;
+       tag: CARDINAL;
+     END;
+
 (* Implementation-module globals (words 10..27). NOT exported: putting them
    in the DEF overflows the compiler name table (proven on real hardware).
    Numbering: DEF vars occupy words 2..9, these follow. *)
 VAR
-    (* word 10 *) ownNamePtr: ADDRESS;
+    (* word 10 *) ownNamePtr: ModuleSymPtr;
     (* word 11 *) spare11: CARDINAL; (* use TBD *)
     (* word 12 *) symHeap: ADDRESS;
     (* word 13 *) scpHeap: ADDRESS;
@@ -89,11 +96,22 @@ TYPE ScopeEntryPtr = POINTER TO ScopeEntry;
        aux1, aux2, aux3: CARDINAL;
      END;
 
-(* 10-byte module-name slot (group C names). *)
-TYPE ModuleSymPtr = POINTER TO ModuleSym;
-     ModuleSym = RECORD
-       name: ARRAY [0..7] OF CHAR;
-       tag: CARDINAL;
+(* Module-name table entry view (10 bytes: name + word). *)
+TYPE SymbolPtr = POINTER TO Symbol;
+
+(* 8-word heap/scope entry view (group C names). Field TYPES serve the
+   dominant use (numerics as CARDINAL/WORD, pointers via transfer); all
+   1-word loads/stores are byte-identical regardless of declared type. *)
+TYPE EntryPtr = POINTER TO Entry;
+     Entry = RECORD
+       w0: CARDINAL;
+       w1: CARDINAL;    (* slot/memo/count *)
+       w2: CARDINAL;    (* link/address (passed on as T1 via transfer) *)
+       w3: BITSET;
+       w4: CARDINAL;    (* kind *)
+       w5: CARDINAL;    (* pointer-ish (link/subtype); passed on as T1 *)
+       w6: CARDINAL;
+       w7: CARDINAL; (* numeric use (arm102 copy); all loads/stores 1 word *)
      END;
 
 (* Import record: same 7-word layout as Compiler.Record; names document use. *)
@@ -122,23 +140,16 @@ TYPE ScopePtr = POINTER TO ScopeDesc;
        idents: Scanner.List;  (* word 1: identifier list *)
      END;
 
-CONST symReadError = 790; (* RAISE code in LibRead {/*0497*/} *)
+(* NOTE: LibRead raises EXCEPTION SYMREADERROR (declared in LoadSymFile);
+   v1.00 assigns the code — verified 790 against the MCD below. *)
 
 (* ---- FORWARDs for the PutAttr/GetAttr <-> EnterChained/AllocSym/
         UnlinkScope/EnterIdent recursion cycle (group B) ---- *)
 (* SymbolAssert/DefineDefinitionModule are DEF-declared: no FORWARD (v1.00 rejects
    a FORWARD duplicating a DEF declaration, proven on hardware). *)
-PROCEDURE CheckNode(node: T1); FORWARD;
 PROCEDURE EnterChained(scope: ADDRESS; node: T1); FORWARD;
-PROCEDURE AllocSym(scope: Words; node: T1); FORWARD;
+PROCEDURE AllocSym(scope: EntryPtr; node: T1); FORWARD;
 PROCEDURE UnlinkScope(scope: ADDRESS; idx: CARDINAL); FORWARD;
-PROCEDURE EnterIdent(scope: Words; idx: CARDINAL); FORWARD;
-PROCEDURE PutAttr(VAR slot: CARDINAL; src: Words); FORWARD;
-PROCEDURE GetAttr(VAR node: T1; idx: CARDINAL); FORWARD;
-PROCEDURE LookupChain(scopeNo: CARDINAL; key: CARDINAL): T1; FORWARD;
-PROCEDURE InitHeaps; FORWARD;
-PROCEDURE EnterStandards; FORWARD;
-PROCEDURE ImportOne(anchor: ADDRESS; symIdx: CARDINAL); FORWARD;
 
 (* ================= group A: module/scope search (@0001-00d4) ================= *)
 
@@ -153,27 +164,30 @@ PROCEDURE FindModule(slot: CARDINAL): CARDINAL;
 VAR i: CARDINAL;
     key: ModuleSymPtr;
 BEGIN
+  i := 0; (*{/*000e*/} init first (original order) *)
+  key := ADDRESS(modStage) + slot * 10; (*{/*0010*/} &modStage^[slot]; ADDRESS arith, no temp needed *)
   (*{/*0016*/} key.name = ownNamePtr^ -> self *)
-  key := ownNamePtr; (* ADDRESS->pointer assignment is legal on v1.00; transfer-call deref is not *)
-  IF modStage^[slot].name = key^.name THEN RETURN 16 END;
-  i := 0;
+  IF key^.name = ownNamePtr^.name THEN RETURN 16 END;
   WHILE i < moduleCount DO
-    IF modStage^[slot].name = moduleTable^[i].name THEN RETURN i END;
+    IF key^.name = moduleTable^[i].name THEN RETURN i END;
     INC(i); (*{/*0036*/}*)
   END;
   RETURN 17; (*{/*003b*/}*)
 END FindModule;
 
 PROCEDURE EnterModule(slot: CARDINAL): CARDINAL;
-(* proc10 @0040: FindModule(slot); add if absent *)
+(* proc10 @0040: FindModule(slot); add if absent.
+   ent caches &modStage^[slot] (original computes once, reuses twice). *)
 VAR found: CARDINAL;
+    ent: SymbolPtr;
 BEGIN
   found := FindModule(slot); (*{/*0042*/}*)
   IF found <= 16 THEN
+    ent := ADR(modStage^[slot]);
     (*{/*0051*/} ghost check on Symbol.word fields *)
-    IF (modStage^[slot].word # 0)
-       AND (moduleTable^[found].word # modStage^[slot].word) THEN
-      Errors.ReportErrorWithText(12, modStage^[slot].name); (*{/*0061*/}*)
+    IF (ent^.word # 0)
+       AND (moduleTable^[found].word # ent^.word) THEN
+      Errors.ReportErrorWithText(12, ent^.name); (*{/*0061*/}*)
     END;
     RETURN found; (*{/*0066*/}*)
   END;
@@ -188,11 +202,12 @@ PROCEDURE LookupChain(scopeNo: CARDINAL; key: CARDINAL): T1;
 (* proc11 @0083 *)
 VAR p: T1;
 BEGIN
-  IF scopeNo > 16 THEN RETURN NIL END; (*{/*0085*/}*)
-  p := scopeChain^[scopeNo]; (*{/*008b*/}*)
-  WHILE p # NIL DO (*{/*008f*/}*)
-    IF CARDINAL(p^.link1) = key THEN RETURN p^.link2 END; (*{/*0093*/}*)
-    p := p^.link0; (*{/*009d*/}*)
+  IF scopeNo <= 16 THEN (*{/*0085*/} single-exit shape (original falls through to one end RETURN) *)
+    p := scopeChain^[scopeNo]; (*{/*008b*/}*)
+    WHILE p # NIL DO (*{/*008f*/}*)
+      IF CARDINAL(p^.link1) = key THEN RETURN p^.link2 END; (*{/*0093*/}*)
+      p := p^.link0; (*{/*009d*/}*)
+    END;
   END;
   RETURN NIL; (*{/*00a2*/}*)
 END LookupChain;
@@ -206,11 +221,25 @@ END MatchSym;
 
 (* ================= group B: allocators + attrs (@00bd-03c9) ================= *)
 
+
+
+
+
+(* NOTE (group B): the brief asked for AllocSym(nodeKind): T1, but the MCode
+   (proc14 @01d7, ProcLeave(2), uses param1+param2) is a 2-param PROCEDURE
+   chaining a 14-byte entry onto scope and filling it from node. *)
+PROCEDURE AllocSym(scope: EntryPtr; node: T1);
+(* proc14 @01d7 — chain-allocate a 14-byte symbol-table entry from node.
+   Entry/Record views only (LSW shapes); node used directly as T1. *)
+VAR se: EntryPtr; n: T1; sv: Compiler.RecordPtr;
+(* ---- nested in AllocSym (original ec-downward calls) ---- *)
 PROCEDURE CheckNode(node: T1);
-(* proc15 @00bd — guard before chaining through word5/word6 fields *)
+(* proc15 @00bd — guard before chaining through word5/word6 fields.
+   Local EXCEPTION: v1.00 assigns the code — verify 272 against MCD. *)
+EXCEPTION NodeErr;
 BEGIN
   IF (node <> NIL) AND (CARDINAL(node^.link0) - 1 < 64) THEN (*{/*00bf*/}*)
-    HALT; (* MCode Raise(272,0,0) {/*00cb*/} *)
+    RAISE NodeErr; (*{/*00cb*/}*)
   END;
 END CheckNode;
 
@@ -229,71 +258,119 @@ BEGIN
   END;
 END NewString;
 
-PROCEDURE GetName(VAR name: ADDRESS; off: CARDINAL);
-(* proc21 @0268 — inverse of NewString: heap copy of pooled string *)
-VAR cs: StringPtr;
-BEGIN
-  IF off <> 0 THEN (*{/*026a*/}*)
-    cs := strHeap + off; (* ADDRESS arithmetic into pointer; transfer-deref is not a VAR actual *)
-    Scanner.CopyStr(name, cs^); (*{/*026e*/}*)
-  ELSE
-    name := NIL; (*{/*027b*/}*)
-  END;
-END GetName;
-
-PROCEDURE PutAttr(VAR slot: CARDINAL; src: Words);
-(* proc18 @0106 — store a type/attr node into the scope-descriptor table.
-   src is Words-typed (not T1): v1.00 rejects indexing a transfer result,
-   so callers transfer (zero-cost, W-probes) and the body indexes directly. *)
-VAR i: CARDINAL; e: Words; sw: Words;
+PROCEDURE PutAttr(VAR slot: CARDINAL; src: T1);
+(* proc18 @0106, nested in AllocSym — store a type/attr node into the
+   scope-descriptor table. Views are RECORD fields (LSW shapes), never
+   Words indexes (LXW): original threads pointers on the M-stack. *)
+VAR i: CARDINAL; e: EntryPtr; sv: Compiler.RecordPtr; sw7: Words;
 BEGIN
   slot := 0; (*{/*0108*/}*)
-  IF src = NIL THEN RETURN END; (*{/*010b*/}*)
-  slot := src^[1]; (*{/*010f*/}*)
-  IF slot <> 0 THEN RETURN END; (* named: referenced by name, no entry *)
-  IF CARDINAL(src) < CARDINAL(symHeap) THEN (* static node: dedup {/*0118*/} *)
-     (* group B read this bound as global word2; reconciled to symHeapBase:
-        word2 is symOptions: BITSET per SYMTAB.SYM, and the heap base is the
-        only agreed boundary for "points outside the dynamic heap". *)
+  IF src = NIL THEN RETURN END; (*{/*010b*/} orig emits not_zero here (v2 peephole); v1.00 emits eq0 *)
+  slot := CARDINAL(src^.link1); (*{/*010f*/} word1 via link1 field *)
+  IF slot <> 0 THEN RETURN END; (*{/*0116*/} named: referenced by name, no entry *)
+  IF CARDINAL(src) < CARDINAL(symOptions) THEN (* static node: dedup {/*0118*/} word2 bound *)
     i := 1; (*{/*011d*/}*)
     WHILE i <= scopeTop DO
-      e := Words(scpHeap + 16 * i); (* temp: transfer results can't be indexed *)
-      IF e^[1] = CARDINAL(src) THEN (*{/*011f*/}*)
+      e := EntryPtr(scpHeap + i * 16);
+      IF e^.w1 = CARDINAL(src) THEN (*{/*011f*/}*)
         slot := i; RETURN;
       END;
       INC(i); (*{/*012e*/}*)
     END;
     SymbolAssert(i <= scopeCap, 86); (*{/*0137*/}*)
     scopeTop := i;
-    e := Words(scpHeap + 16 * scopeTop);
-    e^[1] := CARDINAL(src); (*{/*0141*/}*)
+    e := EntryPtr(scpHeap + scopeTop * 16);
+    e^.w1 := CARDINAL(src); (*{/*0141*/}*)
     slot := scopeTop; (*{/*0149*/}*)
     RETURN;
   END;
   SymbolAssert(scopeTop < scopeCap, 86); (*{/*014f*/}*)
   INC(scopeTop);
   slot := scopeTop; (*{/*015c*/}*)
-  e := Words(scpHeap + 16 * scopeTop);
-  sw := src;
-  e^[1] := 0; (*{/*016b*/}*)
-  PutAttr(e^[2], Words(sw^[2])); (* copy link2 chain {/*016e*/} *)
-  e^[3] := sw^[3]; (*{/*0176*/}*)
-  e^[4] := sw^[4]; (* kind {/*017a*/} *)
-  CASE sw^[4] OF (* kind switch {/*01b3*/} *)
+  e := EntryPtr(scpHeap + scopeTop * 16);
+  sv := Compiler.RecordPtr(src);
+  sw7 := Words(src);
+  e^.w1 := 0; (*{/*016b*/}*)
+  i := e^.w2; PutAttr(i, T1(e^.w2)); (* copy link2 chain {/*016e*/} i reuses dead loop var *)
+  e^.w3 := src^.word3; (*{/*0176*/} BITSET direct *)
+  e^.w4 := src^.word4; (* kind {/*017a*/} *)
+  CASE src^.word4 OF (* kind switch {/*01b3*/} *)
   | 90, 91:
-    e^[5] := sw^[5]; e^[6] := sw^[6]; (*{/*0182*/}*)
+    e^.w5 := CARDINAL(sv^.word5); e^.w6 := CARDINAL(sv^.high); (*{/*0182*/} CARDINAL() zero-cost views *)
   | 99, 100:
-    CheckNode(T1(sw^[5])); (*{/*0192*/}*)
-    EnterChained(ADR(e^[5]), T1(sw^[5])); (*{/*0197*/}*)
+    CheckNode(T1(sv^.word5)); (*{/*0192*/}*)
+    EnterChained(ADR(e^.w5), T1(sv^.word5)); (*{/*0197*/}*)
   | 101:
-    PutAttr(e^[5], Words(sw^[5])); (*{/*0189*/}*)
+    i := e^.w5; PutAttr(i, T1(e^.w5)); (*{/*0189*/}*)
   | 102:
-    PutAttr(e^[5], Words(sw^[5])); (*{/*019e*/}*)
-    PutAttr(e^[6], Words(sw^[6]));
-    e^[7] := sw^[7];
+    i := e^.w5; PutAttr(i, T1(e^.w5)); (*{/*019e*/}*)
+    i := e^.w6; PutAttr(i, T1(e^.w6));
+    e^.w7 := sw7^[7];
   ELSE (* 92..98: name/link/attr words suffice *)
   END;
 END PutAttr;
+
+BEGIN
+  SymbolAssert(symTop < symCap, 87); (*{/*01d9*/}*)
+  INC(symTop);
+  se := EntryPtr(symHeap + symTop * 14); (*{/*01e3*/}*)
+  n := T1(se); sv := Compiler.RecordPtr(node);
+  se^.w0 := scope^.w0; (*{/*01e9*/}*)
+  scope^.w0 := symTop; (*{/*01ed*/}*)
+  NewString(se^.w1, node^.link1); (*{/*01f0*/}*)
+  PutAttr(se^.w2, node^.link2); (*{/*01f7*/} T1 direct, no view needed *)
+  n^.word3 := node^.word3; (*{/*01fe*/}*)
+  se^.w5 := CARDINAL(sv^.word5); se^.w6 := CARDINAL(sv^.high); (* dword {/*0202*/} *)
+  n^.word4 := node^.word4; (* kind {/*0208*/} *)
+  IF CARDINAL(node^.link2) = CARDINAL(Compiler.charArrayDesc) THEN (* COMPILER.word12 {/*020c*/}; CARDINAL compare: T1 vs RecordPtr rejected *)
+    NewString(se^.w5, ADDRESS(sv^.word5)); (*{/*0213*/}*)
+  ELSIF n^.word4 >= 5 THEN (*{/*021c*/}*)
+    CheckNode(T1(sv^.high)); (*{/*0222*/}*)
+    se^.w6 := 0; (*{/*0226*/}*)
+    EnterChained(ADR(se^.w6), T1(sv^.high)); (*{/*0229*/}*)
+  ELSIF (n^.word4 = 1) AND (CARDINAL(node^.link2) = CARDINAL(Compiler.LongrealType)) THEN
+    (* COMPILER.word11: 8-byte real value {/*0231*/} *)
+    SymbolAssert(strTop + 8 <= strCap, 85); (*{/*023e*/}*)
+    MOVE(ADDRESS(sv^.word5), strHeap + strTop, 8); (*{/*0248*/}*)
+    INC(strTop, 8); (*{/*0251*/}*)
+  END;
+END AllocSym;
+
+PROCEDURE EnterChained(scope: ADDRESS; node: T1);
+(* proc13 @0259 — append a link0 node chain to scope (oldest first) *)
+BEGIN
+  IF node <> NIL THEN (*{/*025b*/}*)
+    EnterChained(scope, node^.link0); (*{/*025f*/}*)
+    AllocSym(EntryPtr(scope), node); (*{/*0263*/}*)
+  END;
+END EnterChained;
+
+PROCEDURE EnterIdent(scope: Words; idx: CARDINAL);
+(* proc20 @0305 — declare symbol-table entry idx into scope (dup check + attrs).
+   scope is Words-typed (same transfer-index rule as PutAttr/AllocSym). *)
+VAR sentry: Words;          (* symHeapBase + idx*14 *)
+    new: T1; nw: Words;
+    mark: ADDRESS;
+    nm, nm2: ADDRESS;       (* heap names via GetName *)
+    nmt: StringPtr;
+    vw: Words;              (* temp for T1-field views *)
+    rp: Compiler.RecordPtr; (* temp: transfer results can't be dereferenced; also FindIden result *)
+(* ---- nested in EnterIdent (original ec-downward calls) ---- *)
+PROCEDURE GetName(VAR name: ADDRESS; off: CARDINAL);
+(* proc21 @0268 — inverse of NewString: heap copy of pooled string.
+   View is [0..128] (HIGH 128 pushes 8d 80, proven vs orig 0274): heap
+   strings are NUL-terminated well below, so behavior is identical. *)
+TYPE CS129 = POINTER TO ARRAY [0..128] OF CHAR;
+VAR cs: CS129;
+BEGIN
+  IF off <> 0 THEN (*{/*026a*/}*)
+    cs := strHeap + off; (* ADDRESS arithmetic into pointer; transfer-deref is not a VAR actual *)
+    Scanner.CopyStr(name, cs^); (*{/*026e*/}*)
+  ELSE
+    name := NIL; (*{/*027b*/}*)
+  END;
+END GetName;
+
 PROCEDURE GetAttr(VAR node: T1; idx: CARDINAL);
 (* proc22 @0281 — load a scope entry back into a heap node (inverse of PutAttr).
    Entry word1 = static node pointer or memoized node (returned as-is). *)
@@ -327,59 +404,6 @@ BEGIN
   END;
 END GetAttr;
 
-(* NOTE (group B): the brief asked for AllocSym(nodeKind): T1, but the MCode
-   (proc14 @01d7, ProcLeave(2), uses param1+param2) is a 2-param PROCEDURE
-   chaining a 14-byte entry onto scope and filling it from node. *)
-PROCEDURE AllocSym(scope: Words; node: T1);
-(* proc14 @01d7 — chain-allocate a 14-byte symbol-table entry from node.
-   scope is Words-typed (not ADDRESS): v1.00 rejects indexing a transfer
-   result; callers transfer (zero-cost). *)
-VAR s: Words; n: T1; nw: Words; sw: Words;
-BEGIN
-  SymbolAssert(symTop < symCap, 87); (*{/*01d9*/}*)
-  INC(symTop);
-  s := Words(symHeap + 14 * symTop); (*{/*01e3*/}*)
-  n := T1(s); nw := s; sw := Words(node);
-  s^[0] := scope^[0]; (*{/*01e9*/}*)
-  scope^[0] := symTop; (*{/*01ed*/}*)
-  NewString(s^[1], node^.link1); (*{/*01f0*/}*)
-  PutAttr(s^[2], Words(node^.link2)); (*{/*01f7*/}*)
-  n^.word3 := node^.word3; (*{/*01fe*/}*)
-  nw^[5] := sw^[5]; nw^[6] := sw^[6]; (* dword {/*0202*/} *)
-  n^.word4 := node^.word4; (* kind {/*0208*/} *)
-  IF CARDINAL(node^.link2) = CARDINAL(Compiler.charArrayDesc) THEN (* COMPILER.word12 {/*020c*/}; CARDINAL compare: T1 vs RecordPtr rejected *)
-    NewString(nw^[5], ADDRESS(sw^[5])); (*{/*0213*/}*)
-  ELSIF nw^[4] >= 5 THEN (*{/*021c*/}*)
-    CheckNode(T1(sw^[6])); (*{/*0222*/}*)
-    nw^[6] := 0; (*{/*0226*/}*)
-    EnterChained(ADR(nw^[6]), T1(sw^[6])); (*{/*0229*/}*)
-  ELSIF (nw^[4] = 1) AND (CARDINAL(node^.link2) = CARDINAL(Compiler.LongrealType)) THEN
-    (* COMPILER.word11: 8-byte real constant {/*0231*/} *)
-    SymbolAssert(strTop + 8 <= strCap, 85); (*{/*023e*/}*)
-    MOVE(ADDRESS(sw^[5]), strHeap + strTop, 8); (*{/*0248*/}*)
-    INC(strTop, 8); (*{/*0251*/}*)
-  END;
-END AllocSym;
-
-PROCEDURE EnterChained(scope: ADDRESS; node: T1);
-(* proc13 @0259 — append a link0 node chain to scope (oldest first) *)
-BEGIN
-  IF node <> NIL THEN (*{/*025b*/}*)
-    EnterChained(scope, node^.link0); (*{/*025f*/}*)
-    AllocSym(Words(scope), node); (*{/*0263*/}*)
-  END;
-END EnterChained;
-
-PROCEDURE EnterIdent(scope: Words; idx: CARDINAL);
-(* proc20 @0305 — declare symbol-table entry idx into scope (dup check + attrs).
-   scope is Words-typed (same transfer-index rule as PutAttr/AllocSym). *)
-VAR sentry: Words;          (* symHeapBase + idx*14 *)
-    new: T1; nw: Words;
-    mark: ADDRESS;
-    nm, nm2: ADDRESS;       (* heap names via GetName *)
-    nmt: StringPtr;
-    vw: Words;              (* temp for T1-field views *)
-    rp: Compiler.RecordPtr; (* temp: transfer results can't be dereferenced; also FindIden result *)
 BEGIN
   sentry := Words(symHeap + 14 * idx); (*{/*0307*/}*)
   new := Scanner.NewNode(sentry^[4]); (* kind; always 14 bytes {/*030d*/} *)
@@ -500,6 +524,7 @@ END EnterStandards;
 
 PROCEDURE LoadSymFile(VAR modName: ARRAY OF CHAR);
 (* proc26 @04a0 *)
+EXCEPTION SYMREADERROR;
 VAR
   libFile: Loader.LibFile;  (* 292-byte handle {/*04a8*/}; see LOADER.DEF *)
   header: ARRAY [0..7] OF CARDINAL; (* 16-byte .SYM header {/*04ad*/} *)
@@ -510,7 +535,7 @@ VAR
   (* proc27 @048a, nested: libFile via parent frame *)
   BEGIN
     IF NOT Loader.LibRead(libFile, buf, n) THEN
-      RAISE symReadError; (* 790 {/*0497*/} *)
+      RAISE SYMREADERROR; (* 790 {/*0497*/} — code verified against MCD below *)
     END;
   END LibRead;
 
@@ -530,7 +555,7 @@ BEGIN
   savSym := header[5]; (*{/*04ef*/}*)
   savStr := header[6]; (*{/*04f3*/}*)
   LibRead(modStage, namTotal * 10); (* module-name directory {/*04f7*/} *)
-  dest := modStage + 160; (* slot 16 {/*04fe*/} *)
+  dest := ADR(modStage^[16]); (* slot 16 {/*04fe*/} — ADDRESS of element, not pointer arithmetic *)
   FOR i := 0 TO 7 DO dest^.name[i] := 0C END; (*{/*0505*/}*)
   i := 0;
   WHILE (i <= 7) AND (modName[i] # 0C) DO (*{/*050e*/}*)
@@ -562,6 +587,7 @@ VAR
   s: ScopeEntryPtr;
   ch: ChainPtr;
   matched: CARDINAL;
+  sp: StringPtr; (* temp for VAR-array actuals (transfer-deref banned) *)
 
   PROCEDURE FindExport(start: CARDINAL; name: ADDRESS): CARDINAL;
   (* proc29 @0553, nested *)
@@ -570,8 +596,8 @@ VAR
     idx := start;
     WHILE idx # 0 DO
       e := symHeap + idx * 14; (*{/*0559*/}*)
-      IF Scanner.StrCmp(StringPtr(symHeap + strHeap + e^.nameOff)^,
-                        StringPtr(name)^, 9 IN Scanner.scanOpt) THEN (*{/*0568*/}*)
+      IF Scanner.StrCmp(symHeap + strHeap + e^.nameOff,
+                        name, 9 IN Scanner.scanOpt) THEN (*{/*0568*/} StrCmp takes ADDRESS: no view needed *)
         RETURN idx;
       END;
       idx := e^.link; (*{/*0570*/}*)
@@ -581,7 +607,7 @@ VAR
 
   PROCEDURE ListExports(first: CARDINAL);
   (* proc30 @0579, nested: colCount via parent frame *)
-  VAR e: SymEntryPtr;
+  VAR e: SymEntryPtr; sp: StringPtr;
   BEGIN
     IF first # 0 THEN
       e := symHeap + first * 14;
@@ -591,7 +617,8 @@ VAR
           Texts.WriteLn(3); colCount := 0;
         END;
         Texts.SetCol(3, colCount * 16); (*{/*05a0*/}*)
-        Texts.WriteString(3, StringPtr(strHeap + e^.nameOff)^); (*{/*05af*/}*)
+        sp := strHeap + e^.nameOff;
+        Texts.WriteString(3, sp^); (*{/*05af*/}*)
         INC(colCount); (*{/*05b4*/}*)
       END;
     END;
@@ -616,30 +643,31 @@ BEGIN
   caseFlag := 9 IN Scanner.scanOpt;
   importEvery := 10 IN imp^.flags;
   sysAdr := ADDRESS(Compiler.systemModule); sysImp := sysAdr;
-  IF Scanner.StrCmp(StringPtr(imp^.name)^, StringPtr(sysImp^.name)^, caseFlag) THEN (*{/*05e9*/}*)
+  IF Scanner.StrCmp(imp^.name, sysImp^.name, caseFlag) THEN (*{/*05e9*/} StrCmp takes ADDRESS *)
     IF importEvery THEN (*{/*05f5*/}*)
       imp^.flags := sysImp^.flags; (*{/*05f8*/}*)
       imp^.anchor := sysImp^.anchor; (*{/*0604*/}*)
       Compiler.compilationActive := FALSE; (* word 26 {/*0608*/} *)
     ELSE
-      DefineDefinitionModule(Compiler.systemScopeDesc); (* proc2 {/*060a*/} *)
+      DefineDefinitionModule(CARDINAL(Compiler.systemScopeDesc)); (* proc2 {/*060a*/}: word23 = systemScopeDesc slot *)
     END;
     RETURN; (*{/*060e*/}*)
   END;
   sysAdr := ADDRESS(Compiler.storageModule); stoImp := sysAdr;
-  IF Scanner.StrCmp(StringPtr(imp^.name)^, StringPtr(stoImp^.name)^, caseFlag) THEN (*{/*0611*/}*)
+  IF Scanner.StrCmp(imp^.name, stoImp^.name, caseFlag) THEN (*{/*0611*/} StrCmp takes ADDRESS *)
     IF importEvery THEN (*{/*061d*/}*)
       imp^.flags := stoImp^.flags;
       imp^.anchor := stoImp^.anchor;
       Compiler.compilationActive := FALSE;
     ELSE
-      DefineDefinitionModule(Compiler.storageScopeDesc); (*{/*062e*/}*)
+      DefineDefinitionModule(CARDINAL(Compiler.storageScopeDesc)); (*{/*062e*/}: word24 *)
     END;
     RETURN;
   END;
   stgFlag := 1; (* word 25 {/*0635*/}; group C reads compileActive *)
   InitHeaps; (*{/*0638*/}*)
-  LoadSymFile(StringPtr(imp^.name)^);
+  sp := imp^.name;
+  LoadSymFile(sp^);
   modIdx := EnterModule(16); (*{/*0640*/}*)
   SymbolAssert(modIdx # 16, 48); (*{/*0644*/}*)
   expIdx := expHead; (*{/*064b*/}*)

+ 59 - 0
tools/score.py

@@ -0,0 +1,59 @@
+"""MCD proc-compare scorer. Usage: score.py ORIG_TXT OURS_TXT MOD PAIRS...
+PAIRS = OrigName:OursName. Masks absolute targets, proc refs, dep slots;
+leave-truncates bodies; reports SAME/PREFIX/DIFF per pair."""
+import re, sys
+
+def procs(path, mod):
+    txt = open(path, errors='replace').read()
+    heads = [(m.group(1), int(m.group(2), 16))
+             for m in re.finditer(r'(?m)^;? ?' + mod + r'\.(\w+): ([0-9a-f]{4})$', txt)]
+    ops = []
+    for line in txt.splitlines():
+        m = re.match(r'^\s*([0-9a-f]{4})\s+((?:[0-9a-f]{2}\s*)+?)(?:\s{2,}|\t+|$)(.*)$', line)
+        if m:
+            ops.append((int(m.group(1), 16), m.group(2).split(), m.group(3).strip()))
+    out = {}
+    addrs = sorted(set(a for _, a in heads))
+    for name, a in heads:
+        if name in out:
+            continue
+        nxt = min([x for x in addrs if x > a] or [0x10000])
+        out[name] = [(o, b, m) for (o, b, m) in ops if a <= o < nxt and b != ['00'] and m]
+    return out
+
+def norm(m):
+    m = re.sub(r'\b0[0-9a-f]{3}\b', '@@@@', m)
+    m = re.sub(r'call_with_frame proc\d+', 'call_with_frame proc##', m)
+    m = re.sub(r'nested_call proc\d+', 'nested_call proc##', m)
+    m = re.sub(r'(?<!_)proc\d+', 'proc##', m)
+    return m
+
+def trunc(ms):
+    li = [i for i, m in enumerate(ms)
+          if m.split()[0] in ('leave', 'leave0', 'leave2', 'leave4', 'leave6',
+                              'fct_leave', 'longfct_leave', 'end_program', 'Raise')]
+    return ms[:li[-1] + 1] if li else ms
+
+def main():
+    orig_txt, ours_txt, mod = sys.argv[1], sys.argv[2], sys.argv[3]
+    A, B = procs(orig_txt, mod), procs(ours_txt, mod)
+    same = tot = 0
+    for pair in sys.argv[4:]:
+        o, u = pair.split(':')
+        tot += 1
+        if o not in A:
+            print(f'{u}/{o} ORIG-MISSING'); continue
+        if u not in B:
+            print(f'{u}/{o} OURS-MISSING'); continue
+        xa, xb = trunc([norm(m) for (_, _, m) in A[o]]), trunc([norm(m) for (_, _, m) in B[u]])
+        n = min(len(xa), len(xb))
+        if xa == xb:
+            same += 1
+            print(f'{u}/{o} {len(xa)} vs {len(xb)} SAME')
+        elif xa[:n] == xb[:n]:
+            print(f'{u}/{o} {len(xa)} vs {len(xb)} PREFIX(+{abs(len(xa)-len(xb))})')
+        else:
+            print(f'{u}/{o} {len(xa)} vs {len(xb)} DIFF')
+    print(f'{same}/{tot} exact')
+
+main()