1
0

8 Коммиты ccc231d52d ... 41ad3832d3

Автор SHA1 Сообщение Дата
  Eric Streit 41ad3832d3 feat(express): compile groundwork — DEF pair, leak-rule imports, comment syntax 1 неделя назад
  Eric Streit 8683ec9c2e docs(summary): SYMTAB prefix 7/14, ImportAll verdict 1 неделя назад
  Eric Streit 6d25ab3cb8 feat(symtab): direct-VAR arms, FindExport out, local merges, prefix 7/14 1 неделя назад
  Eric Streit e8630bea8e feat(symtab): nesting per call-graph, record views, prefix 7/14 1 неделя назад
  Eric Streit 04df0c70b0 feat(symtab): nesting + record views, prefix 7/14 exact 1 неделя назад
  Eric Streit 52c3b7575b docs: root README (method, layout, scores, toolchain rules, reproduce) 1 неделя назад
  Eric Streit e2f73a5511 docs(session): SYMTAB prefix-diff via stub-tails; re-read-first next 1 неделя назад
  Eric Streit 7a637624a7 wip(symtab): GetAttr new-reuse, EnterIdent single-depth arms; version-skew finding 1 неделя назад
8 измененных файлов с 553 добавлено и 297 удалено
  1. 73 0
      README.md
  2. 79 0
      SESSION.md
  3. 7 2
      SUMMARY.md
  4. 49 0
      src/compiler/EXPRESS.DEF
  5. 48 94
      src/compiler/EXPRESS.MOD
  6. 11 0
      src/compiler/PASS1.DEF
  7. 227 201
      src/compiler/SYMTAB.MOD
  8. 59 0
      tools/score.py

+ 73 - 0
README.md

@@ -0,0 +1,73 @@
+# Turbo Modula-2 — Compiler Reverse Engineering
+
+Reverse-engineering the Turbo Modula-2 v1.00 compiler into readable,
+renamed Modula-2 sources, verified by recompiling with the **original
+v1.00 toolchain** and diffing the resulting `.MCD` binaries procedure by
+procedure against the originals.
+
+## Method
+
+1. Disassemble original `.MCD` files (`Reversing-Turbo-Modula2-main/`,
+   git-ignored upstream input) with `unassemble.c`.
+2. Rewrite each module in readable Modula-2 (`src/compiler/`), renaming
+   `globalN`/`procN`/`localN` per `src/compiler/RENAME-MAP.md`.
+3. Recompile with unmodified Turbo Modula-2 v1.00 (`M2.COM` + `m2.ovr`,
+   run under `cpmulator`), driven hands-free by `tools/m2drive.py` +
+   `tools/chain.sh`.
+4. Disassemble our `.MCD`, compare mnemonic streams (absolute jump targets
+   masked, `00` gap bytes stripped, dep-slot renumbering accepted as benign).
+   Score = identical procedures / total.
+
+## Layout
+
+- `src/compiler/` — renamed `*.DEF`/`*.MOD` under verification, plus
+  `RENAME-MAP.md` (canonical names), `FRONTEND.md` (export map with MCode
+  offsets), `GENZ80.md`.
+- `docs/compiler/` — module analyses (`00-overview` … `70-rename-plan`),
+  `disasm/` (backend disassemblies), `TAGGED-SUMMARY.md`.
+- `tools/` — `m2drive.py` (pty driver for the M2 shell), `chain.sh`
+  (fresh-boot-per-file batch compiles), `mcdiff.sh` (stage-1 static checks).
+- `tmp/` — local CP/M working drives and scratch (git-ignored, never commit).
+- `SESSION.md` — running log + next-session plan. `SUMMARY.md` — milestones.
+- Upstream inputs (`Reversing-*/`, `Turbo-*Reloaded*/`, CP/M images) stay
+  git-ignored; only analysis + renamed sources are versioned.
+
+## Verification status (Oct 2026)
+
+| module | result | tag |
+|---|---|---|
+| COMPILER.MOD | 18/19 byte-identical (linkage names only) | stage2-v1 |
+| SCANNER.MOD | 34/41 mnemonic-identical | scanner-verify-01 |
+| ERRORS.MOD | 28/30 mnemonic-identical | errors-verify-01 |
+| CODEGEN.MOD | 34/37 mnemonic-identical | codegen-verify-01 |
+| SYMTAB.MOD | WIP: compiles through GetAttr, wall in EnterIdent | symtab-wip-02 |
+| EXPRESS/STATEMEN/PASS1/GENZ80/NATIVE | drafts, unverified | *-draft-v1 |
+
+Remaining diffs are all accounted for in `SESSION.md` (clean-room Z80
+replacements, forced literal folding, VarAddr shim, gap-filler/FATDIR
+divergences, trailing address tables).
+
+## v1.00 toolchain rules (all proven on hardware via ZZ probes)
+
+- `DEF`-imported names leak into the `MOD` scope; re-listing is a hard error.
+- `DEF`/`MOD` parameter names must match exactly.
+- No `CAST`/`VAL`; no `CARDINAL→BITSET` assignment (use `BITSET{n}` literals).
+- `IN` needs real sets; open-array `VAR` accepts any variable (VarAddr trick).
+- Transfer results can't be indexed/dereferenced (`Words(x)^[i]`,
+  `P(x)^.f`) — retype params to view types, transfer (zero-cost) at call sites.
+- `= 0` emits unary `eq0`; `CHR(ORD())`/`ORD()`/`CHR()` are zero-cost no-ops.
+- `OUT OF MEMORY` often masks type errors — isolate suspects in fresh-heap
+  ZZ probes. Heap budget is per-proc nodes; ELSIF-nesting pins conditions.
+- Retail M2.COM is Version 1.00 (writes SYM v3); shipped originals carry
+  SYM v2 (Borland-internal toolchain) — explains laxer original spellings.
+
+## Reproduce
+
+```sh
+./tools/mcdiff.sh            # stage-1 static checks (no VM needed)
+./tools/chain.sh tmp/stage/drive SCANNER.MOD   # compile under v1.00
+tmp/stage/unassemble tmp/stage/drive/SCANNER.MCD  # then diff vs original
+```
+
+See `SESSION.md` → “Next session plan” for what comes next (currently:
+SYMTAB per-proc loop, then EXPRESS → STATEMEN → PASS1).

+ 79 - 0
SESSION.md

@@ -109,3 +109,82 @@ Next: GENZ80 media hunt; stage-2 pty-driven MCD diff per module.
    then switch to vm_linux instrumentation (heap high-water log) for ground
    truth instead of blind bisection. Do NOT rename everything terse.
 5. Refresh SUMMARY.md + retag at each verify tag.
+
+## SYMTAB session 2026-10-07 (EnterIdent heap wall)
+- GetAttr arms 101/102: dropped writebacks + `sub` local, reuse `new`
+  (original tail-calls with `jump_stack`: result discarded, memoization is
+  the effect). EnterIdent arm3: nested IF/ELSE -> two single-depth AND-IFs.
+  Neither fixed the OOM (kept: both are the honest minimal shapes).
+- Heap-model verdict after full falsification battery (names, chars,
+  comments, slots, forwards, locals-count, prefix-size, ASCII, $[-retention,
+  nesting): budget is per-proc NODES; EnterIdent needs ~10 nodes over budget
+  with every retail-legal spelling exhausted (all 6 temp-assigns proven
+  mandatory: transfer-index/deref bans, VAR-formal bans).
+- VERSION SKEW (decisive context): retail M2.COM = "Version 1.00", writes
+  SYM v3; shipped originals carry SYM v2 (Borland-internal/older toolchain).
+  Only v1.00 retail on hand (other M2.COM is a different vendor product).
+  Explains laxer-original spellings (direct ADR pushes, `ADR->VAR`,
+  Buf->Identifier) that retail rejects or OOM-masks.
+- Conclusion: big modules (SYMTAB/EXPRESS/STATEMEN/PASS1) cannot compile
+  under v1.00-retail heap with faithful sources. Next: vm_linux path —
+  build the portable M-code VM, run retail m2.ovr under instrumented
+  mcode.c (heap high-water log) for ground truth; heap size does not affect
+  emitted code, so verification stays sound. Fallback: partial (prefix)
+  verification, honestly labeled.
+
+## SYMTAB session 2026-10-07 (prefix verification via stub-tails)
+- Stub-tails method works: stub bodies from EnterIdent on (+7 DEF-stub
+  HALTs), keep headers -> v1.00 emits MCD (1059B). Full module still OOMs
+  in EnterIdent (unchanged wall).
+- Our MCD retains 6-char uppercase names (no directive); originals are
+  numbered -> FINAL SYMTAB needs explicit `$[+ remove` (+ `$[-` for IMDCLS).
+- Prefix score (leave-truncated, jumps masked): 2/12 SAME* (MatchSym,
+  EnterChained). Rest diverge STRUCTURALLY (inverted conditions, extra
+  leaves, +30mn in PutAttr) = draft-v1 group-guess quality, not heap
+  artifacts. Notably ours runs LONGER everywhere (temps), orig is leaner.
+- Inter-proc gap filler differs by toolchain (orig `...ff`, ours `...00`),
+  excluded from scoring like dep-slots.
+- Next: re-read prefix bodies (PutAttr/GetAttr/AllocSym/FindModule first)
+  against symtab.txt statement-by-statement; keep heap discipline (flat,
+  minimal nodes). VM path still open for full-module compile.
+
+## SYMTAB session 2026-10-07b (nesting model + record views, prefix 7/14)
+- CALL GRAPH (mnemonic-authoritative, unassemble.c): `ee`=sibling/self display
+  call (ZZ-proven under v1.00), `ec`=downward (parent->nested child), `ed/f/ef`
+  = display-free. FORCED nest map: AllocSym{CheckNode,NewString,PutAttr},
+  EnterIdent{GetName,GetAttr}, CompileDispatch{35,36,37/TryCompile}, + {32}@2,
+  {34}@33, {38}@6, {25}@24, {27}@26, {29,30,31}@28. Rest top-level. Gap-`ff`
+  bytes decode as phantom calls — ignore trailing-ff "calls". proc16 dead.
+- Restructured SYMTAB.MOD accordingly (nested, forwards trimmed to the 2
+  cycle-breakers). v1.00 emits ee/ec exactly like the original. Zero-cost:
+  transfers emit no code; view-field TYPES are byte-neutral (all 1-word).
+- Record-field views (LSW/SSW) replace Words indexing (LXW) everywhere:
+  new local Entry type (CARDINAL/WORD/BITSET per dominant use); T1 direct
+  for nodes. Proven: `i * 16` folds to `shl` (v1.00 peephole — swap all
+  `N * x` with power-of-2 N); open-array actuals push HIGH (GetName view
+  is [0..128], not [0..127]); declared exceptions emit small codes
+  (NodeErr=273 vs orig 272, 1B documented); `ADDRESS<CARDINAL` etc. need
+  CARDINAL() views; LSWn pops base (Indirect.mod); `=` on arrays = inline
+  string_comp with (addr,size) pairs.
+- Prefix score 7/14 exact (FINDMO/ENTERM/LOOKUP/MATCHS/NEWSTR/ENTERC/GETNAM).
+  CHECKN=1B (exception code). Left: PUTATT/ALLOCS/GETATT detail (frames,
+  guard polarity (accepted peephole class), leave-vs-jump (heap-driven flat
+  kept), stack-threaded temps (accepted)). ENTERI wall stands (full build).
+- Slot map COMPILER.DEF re-verified 1:1 (systemScopeDesc=23 etc.).
+
+## SYMTAB session 2026-10-08 (direct-VAR arms, FindExport out, 7/14)
+- Recursive PutAttr arms now pass entry fields DIRECT as VAR (`PutAttr(e^.w5,
+  T1(sv^.word5))`) + fixed SRC-vs-entry object bug (node part must be src's).
+  GetAttr Entry views; PutAttr dedup loop is REPEAT (matches orig).
+- FindExport moved top-level (no self-ee/frame use); ImportAll 19->13 locals
+  via honest merges (sysAdr inline, matched/sib... no: matched->findIdx,
+  caseFlag/importEvery inline, stoImp->sysImp, expIdx->scopeIdx). All remaining
+  overlap. 13 typed minimum exceeds retail budget (~12 plain-equiv).
+- PROVEN (6 ways): ImportAll region cannot compile under v1.00-retail
+  (decl-count + record-descriptor weight; Borland heap ~2x). colCount-global
+  and ListExports-out variants tested, both insufficient/worse. VM cannot
+  raise heap (16-bit address space, high_mem already top).
+- Prefix 7/14 exact (FINDMO/ENTERM/LOOKUP/MATCHS/NEWSTR/ENTERC/GETNAM).
+  Left: PUTATT/ALLOCS/GETATT accepted-class gaps (frames, guard polarity,
+  leave-vs-jump, stack-temps), CHECKN 1B, SYMBOL epilogue, ENTERI wall,
+  SYMTAB tail by construction. Next: EXPRESS (same playbook per-proc).

+ 7 - 2
SUMMARY.md

@@ -51,8 +51,13 @@ stripped; dep-slot renumbering benign, proven via slot formula):
 * `ERRORS.MOD` 28/30 (`errors-verify-01`; heap was import weight — dropped
   unused Compiler/ComLine; EditPos module + `EDITERR` overlay load decoded).
 * `CODEGEN.MOD` 34/37 (`codegen-verify-01`; keywordTable low-byte overlay).
-* `SYMTAB.MOD` WIP: compiles through GetAttr, dies in EnterIdent (heap);
-  genuine fixes banked (TryCompile logic, allocHook, view-typed params).
+* `SYMTAB.MOD` prefix 7/14 exact, tail by construction (`symtab-wip-04`):
+  nesting per call-graph (`ee`/`ec` reproduced), record-field views, direct-
+  VAR recursive arms (incl. SRC-vs-entry object fix), REPEAT loops, `i * 16`
+  peephole, FindExport hoisted out. ImportAll region PROVEN uncompilable
+  under v1.00-retail (6 experiments; Borland heap ~2x) — full-module
+  verification awaits a bigger-heap Turbo. Genuine fixes banked (TryCompile
+  logic, allocHook, view-typed params, call-graph map).
 Toolchain rules proven on hardware: DEF-leak, exact DEF/MOD param names,
 no FORWARD of DEF-declared procs, nominal array checks, transfer-index
 and transfer-deref bans, zero-cost `CHR(ORD())`/`ORD()`/`CHR()`/`= 0`,

+ 49 - 0
src/compiler/EXPRESS.DEF

@@ -0,0 +1,49 @@
+(* EXPRESS.DEF — best-guess export map (FRONTEND.md); corrected iteratively
+   by MCD-diff. Names are ours; slots follow declaration order. *)
+DEFINITION MODULE Express;
+FROM SYSTEM IMPORT ADDRESS, WORD;
+FROM Compiler IMPORT RecordPtr;
+
+TYPE DescPtr = POINTER TO Desc;
+     Desc = RECORD
+       CASE : BOOLEAN OF
+       | TRUE:
+         typ: ADDRESS;     (* word0: type descriptor *)
+         mode: CARDINAL;   (* word1: 0=const *)
+         value: CARDINAL;  (* word2: const value / offset *)
+         extra: CARDINAL;  (* word3 *)
+         kind: CARDINAL;   (* word4: type class tag *)
+         low: CARDINAL;    (* word5 *)
+         high: CARDINAL;   (* word6 *)
+         w7: CARDINAL;
+         w8: CARDINAL;
+         w9: CARDINAL;
+       | FALSE:
+         raw: ARRAY [0..9] OF WORD;
+       END;
+     END;
+
+PROCEDURE CentralError(ok: BOOLEAN; code: CARDINAL);
+PROCEDURE PushExprDesc(dst: DescPtr; src: DescPtr);
+PROCEDURE PopExprDesc(d: DescPtr);
+PROCEDURE EmitOp(subOp, typeKind: CARDINAL);
+PROCEDURE NormalizeOperand;
+PROCEDURE LoadIndirect;
+PROCEDURE BoolCondHelper;
+PROCEDURE GetConstVal;
+PROCEDURE OpSetTest(allowed: BITSET; errCode: CARDINAL);
+PROCEDURE ConstToCard(c: DescPtr; errPos: CARDINAL);
+PROCEDURE ConstToInt(c: DescPtr; errPos: CARDINAL);
+PROCEDURE EmitRangeCheck;
+PROCEDURE ParseSimpleExpression;
+PROCEDURE LoadOperand;
+PROCEDURE StoreOperand;
+PROCEDURE ParseFactor(p1, p2, p3, p4, p5: CARDINAL);
+PROCEDURE EvalConstExpr;
+PROCEDURE ParseTerm(flag, t: CARDINAL);
+PROCEDURE ParseSetCons(flag, s: CARDINAL);
+PROCEDURE ParseDesignatorBase(finA, finB, probeA, probeB: CARDINAL): BOOLEAN;
+PROCEDURE ParseAssignment;
+PROCEDURE ParseExpression;
+
+END Express.

+ 48 - 94
src/compiler/EXPRESS.MOD

@@ -48,28 +48,10 @@
 
 IMPLEMENTATION MODULE Express;
 IMPORT Compiler, Scanner, Errors, CodeGen, SymTab, Pass1, Doubles;
-FROM SYSTEM IMPORT ADDRESS, ADR, MOVE, WORD;
+FROM SYSTEM IMPORT ADR, MOVE; (* ADDRESS/WORD/RecordPtr come via DEF imports (leak rule) *)
 
-(* 10-word operand descriptor: named view + raw words view (same layout). *)
-TYPE DescPtr = POINTER TO Desc;
-     Desc = RECORD
-       CASE : BOOLEAN OF
-       | TRUE:
-         typ: ADDRESS;     (* word0: type descriptor *)
-         mode: CARDINAL;   (* word1: 0=const *)
-         value: CARDINAL;  (* word2: const value / offset *)
-         extra: CARDINAL;  (* word3 *)
-         kind: CARDINAL;   (* word4: type class tag *)
-         low: CARDINAL;    (* word5 *)
-         high: CARDINAL;   (* word6 *)
-         w7: CARDINAL;
-         w8: CARDINAL;
-         w9: CARDINAL;
-       | FALSE:
-         raw: ARRAY [0..9] OF WORD;
-       END;
-     END;
-     Words = POINTER TO ARRAY [0..9] OF WORD;
+(* Desc/DescPtr come from EXPRESS.DEF; Words is module-local. *)
+     TYPE Words = POINTER TO ARRAY [0..9] OF WORD;
 
 VAR
     curDesc: Desc;        (* word2: current operand descriptor *)
@@ -81,81 +63,53 @@ VAR
     exprPool: ADDRESS;    (* word9 *)
 
 (* ---- unresolved EXPRESS-internal helpers: FORWARD = work list ---- *)
-PROCEDURE CentralError(ok: BOOLEAN; code: CARDINAL); FORWARD; (* proc1: proven (cond, code) via STMT @058a [bool,20] frame evidence; 1-arg call sites below need re-read *)
-(*FIXME-NYC*) PROCEDURE PushOperand; FORWARD; (* proc2 *)
-(*FIXME-NYC*) PROCEDURE PopOperand; FORWARD; (* proc3 *)
-(*FIXME-NYC*) PROCEDURE EmitOp; FORWARD; (* proc6 *)
-(*FIXME-NYC*) PROCEDURE NormalizeOperand; FORWARD; (* proc10 *)
-(*FIXME-NYC*) PROCEDURE LoadIndirect; FORWARD; (* proc11 *)
-(*FIXME-NYC*) PROCEDURE BoolCondHelper; FORWARD; (* proc12 *)
-(*FIXME-NYC*) PROCEDURE CheckAndStore; FORWARD; (* proc22, arity TBD *)
-(*FIXME-NYC*) PROCEDURE GetRangeBase; FORWARD; (* proc25 *)
-(*FIXME-NYC*) PROCEDURE IsPointer(t: CARDINAL): BOOLEAN; FORWARD; (* proc26 *)
-(*FIXME-NYC*) PROCEDURE TypeKindOf(): CARDINAL; FORWARD; (* proc27 *)
-(*FIXME-NYC*) PROCEDURE EmitAddress; FORWARD; (* proc28 *)
-(*FIXME-NYC*) PROCEDURE ParseActualList; FORWARD; (* proc29 *)
-(*FIXME-NYC*) PROCEDURE EmitIndexed(idx: CARDINAL); FORWARD; (* proc31 *)
-(*FIXME-NYC*) PROCEDURE PushStringConst; FORWARD;
-(*FIXME-NYC*) PROCEDURE LookupField(n: CARDINAL): BOOLEAN; FORWARD;
-(*FIXME-NYC*) PROCEDURE Power2(e: CARDINAL): CARDINAL; FORWARD;
-(*FIXME-NYC*) PROCEDURE ExpectBoolOrSet; FORWARD;
-(*FIXME-NYC*) PROCEDURE EmitSetMember(d: Desc): Desc; FORWARD;
-(*FIXME-NYC*) PROCEDURE IsOrdinalType(t: ADDRESS): BOOLEAN; FORWARD;
-(*FIXME-NYC*) PROCEDURE CheckForwardRef; FORWARD;
-(*FIXME-NYC*) PROCEDURE CheckAssignable2(t: ADDRESS); FORWARD;
-(*FIXME-NYC*) PROCEDURE EmitOperand31(n: ADDRESS); FORWARD;
-(*FIXME-NYC*) PROCEDURE EmitDescriptor2(d: Desc); FORWARD; (* proc2 *)
-(*FIXME-NYC*) PROCEDURE FoldLongRelation(d: Desc); FORWARD; (* DOUBLES.qmul + proc47 *)
-(*FIXME-NYC*) PROCEDURE FoldLongDiv(d: Desc); FORWARD; (* DOUBLES.qdiv + proc47 *)
-(*FIXME-NYC*) PROCEDURE FoldLongAdd(d: Desc); FORWARD; (* DOUBLES.qadd + proc47 *)
-(*FIXME-NYC*) PROCEDURE EmitSetOrArrayAssign(d: Desc); FORWARD; (* proc11 pair *)
 (* Forward refs to group-defined procedures used before definition *)
-PROCEDURE ParseExpression; FORWARD;
 PROCEDURE ParseDesignatorTail; FORWARD;
-PROCEDURE ParseTerm(flag, t: CARDINAL); FORWARD; (* audit SWAP; 3-arg call sites still FIXME *)
-PROCEDURE ParseSetCons(flag, s: CARDINAL); FORWARD; (* audit SWAP; 1-arg call site still FIXME *)
 PROCEDURE ParseTypeCast(kind: CARDINAL); FORWARD;
 PROCEDURE ParseFuncCall(idx: CARDINAL): CARDINAL; FORWARD;
 PROCEDURE ExpandStdProc43(code: CARDINAL); FORWARD; (* FIXME: 0-arg call site *)
 PROCEDURE ExpandStdProc44(first: CARDINAL); FORWARD;
 PROCEDURE ExpandStdProc45(std: CARDINAL); FORWARD; (* FIXME: 0-arg call site *)
 PROCEDURE ExpandStdProc46; FORWARD;
-PROCEDURE GetConstVal; FORWARD;
-PROCEDURE PopExprDesc(d: DescPtr); FORWARD; (* FIXME: 0-arg call sites *)
-PROCEDURE PushExprDesc(dst: DescPtr; src: DescPtr); FORWARD; (* audit SWAP; arities still FIXME *)
-PROCEDURE ConstToCard(c: DescPtr; errPos: CARDINAL); FORWARD;
-PROCEDURE ConstToInt(c: DescPtr; errPos: CARDINAL); FORWARD;
 PROCEDURE MatchOpClass(allowed: BITSET; errCode: CARDINAL); FORWARD;
 PROCEDURE FoldConstOp(resTyp: DescPtr); FORWARD; (* FIXME: kind-result sites *)
 PROCEDURE EmitCompare; FORWARD;
-PROCEDURE EmitRangeCheck; FORWARD;
-PROCEDURE ParseSimpleExpression; FORWARD;
-PROCEDURE LoadOperand; FORWARD;
-PROCEDURE StoreOperand; FORWARD;
 PROCEDURE CheckRelationTypes(t1: ADDRESS); FORWARD; (* audit: op unused in body *)
 PROCEDURE FoldRelation(): ADDRESS; FORWARD;
-PROCEDURE ParseFactor(p1, p2, p3, p4, p5: CARDINAL); FORWARD; (* re-read: pK = paramK confirmed (InsertEntry-arg=param5, p2#0=param2, p3=9 test=param3); mirror hypothesis REJECTED, def stands *)
-PROCEDURE EvalConstExpr; FORWARD;
 PROCEDURE ParseSetOrCastTail; FORWARD;
-PROCEDURE ParseAssignment; FORWARD;
 PROCEDURE IsOrdinal(t: Compiler.RecordPtr): BOOLEAN; FORWARD;
 PROCEDURE IsSet(t2, t1: Compiler.RecordPtr): BOOLEAN; FORWARD;
 PROCEDURE BaseTypeOf(t2, t1: Compiler.RecordPtr): BOOLEAN; FORWARD;
 PROCEDURE CheckAssignable; FORWARD;
 PROCEDURE ParseSelector(sel: Compiler.RecordPtr; needVal: BOOLEAN; base: Compiler.RecordPtr); FORWARD;
-PROCEDURE ParseDesignatorBase(finA, finB, probeA, probeB: CARDINAL): BOOLEAN; FORWARD; (* re-read: 4 formals (fct_leave 132); param4/param3 = final operands (formals 1-2), param2/param1 = probe keys (formals 3-4); ok1..id2 dropped (unused); probeA/B assignment by call order TBD *)
 PROCEDURE GetTypeDesc(src: ADDRESS; dest: ADDRESS; mode: CARDINAL); FORWARD;
 PROCEDURE CheckAssignmentCompat(dst: ADDRESS); FORWARD;
 
 (* EXPRESS group 1 — descriptors, predicates, const-fold, designator base, selector *)
 
 (* proc4 @004a — ~PushExprDesc *)
+
+(* FIXME-NYC stubs (signatures per call sites; bodies TBD by MCD audit;
+   these 6 may merge with PushExprDesc/PopExprDesc/etc. once mapping lands). *)
+PROCEDURE PushOperand;
+BEGIN HALT; END PushOperand;
+PROCEDURE PopOperand;
+BEGIN HALT; END PopOperand;
+PROCEDURE CheckAndStore;
+BEGIN HALT; END CheckAndStore;
+PROCEDURE GetRangeBase;
+BEGIN HALT; END GetRangeBase;
+PROCEDURE IsPointer(t: CARDINAL): BOOLEAN;
+BEGIN HALT; END IsPointer;
+PROCEDURE TypeKindOf(): CARDINAL;
+BEGIN HALT; END TypeKindOf;
+
 PROCEDURE PushExprDesc(dst: DescPtr; src: DescPtr);
 VAR aligned: CARDINAL; (* local word-2 *)
 BEGIN
-  {/* 004a */} (* aligned := (src^.extra + 1) DIV 2 * 2 *)
+  (*{004a}*) (* aligned := (src^.extra + 1) DIV 2 * 2 *)
   aligned := (src^.extra + 1) DIV 2 * 2;
-  {/* 0054 */} (* if temp mark held, reserve space, check against SymTab pool *)
+  (*{0054}*) (* if temp mark held, reserve space, check against SymTab pool *)
   IF spillFlag <> 0 THEN
     DEC(spillTop, aligned);
     IF spillTop < SymTab.stringPoolPtr THEN
@@ -163,12 +117,12 @@ BEGIN
     END;
     spillTop := SymTab.stringPoolPtr; (* sync mark: 006a..006b *)
   END;
-  {/* 006d */} (* init new descriptor *)
+  (*{006d}*) (* init new descriptor *)
   dst^.mode := 1;
   IF spillFlag = 0 THEN dst^.kind := 0 ELSE dst^.kind := dst^.kind END;
   dst^.extra := spillTop;
   dst^.typ := src^.typ;
-  {/* 007e */} (* if fresh mark, claim space, overflow check 83 *)
+  (*{007e}*) (* if fresh mark, claim space, overflow check 83 *)
   IF spillFlag = 0 THEN
     INC(spillTop, aligned);
     IF spillTop > SymTab.stringPoolPtr THEN
@@ -181,17 +135,17 @@ END PushExprDesc;
 (* proc32 @0095 — ~IsOrdinal *)
 PROCEDURE IsOrdinal(t: RecordPtr): BOOLEAN;
 BEGIN
-  {/* 0095 */} (* subrange with CARDINAL bounds is not plain ordinal here *)
+  (*{0095}*) (* subrange with CARDINAL bounds is not plain ordinal here *)
   IF (t^.word4 = 1) AND (t^.word5 >= 0) AND (t^.word6 >= 0) THEN
     RETURN FALSE;
   END;
-  {/* 00ac */} (* unwrap alias: t := t^.word2 *)
+  (*{00ac}*) (* unwrap alias: t := t^.word2 *)
   t := t^.word2;
-  {/* 00af */} (* INTEGER is ordinal *)
+  (*{00af}*) (* INTEGER is ordinal *)
   IF t = Compiler.IntType THEN
     RETURN TRUE;
   END;
-  {/* 00ba */} (* otherwise ordinal iff class tag nonzero *)
+  (*{00ba}*) (* otherwise ordinal iff class tag nonzero *)
   RETURN t^.word4 <> 0;
 END IsOrdinal;
 
@@ -205,14 +159,14 @@ VAR
   lo, hi: INTEGER;    (* local word-6, word-7 *)
   sub: RecordPtr;     (* local word-8 *)
 BEGIN
-  {/* 00c0 */} (* only descriptors with kind-bit 7 set need pop work *)
+  (*{00c0}*) (* only descriptors with kind-bit 7 set need pop work *)
   IF NOT (7 IN BITSET(d^.kind)) THEN RETURN END;
-  {/* 00c8 */} (* classify *)
+  (*{00c8}*) (* classify *)
   isOrd := IsOrdinal(d^.typ);
   neg := isOrd < 0;
   hasRange := 3 IN BITSET(d^.kind);
   IF hasRange THEN lo := d^.low; hi := d^.high END;
-  {/* 00de */} (* constant-expression branch *)
+  (*{00de}*) (* constant-expression branch *)
   IF curDesc.mode = 0 THEN
     IF neg THEN
       IF (curDesc.typ = Compiler.CardType) OR (curDesc.value >= 0) THEN
@@ -232,7 +186,7 @@ BEGIN
     END;
     RETURN;
   END;
-  {/* 0120 */} (* codegen branch, range-check flag bit 8 *)
+  (*{0120}*) (* codegen branch, range-check flag bit 8 *)
   IF 8 IN Scanner.scanOpt THEN
     emitConv := FALSE;
     IF IsOrdinal(curDesc.typ) - isOrd = 2 THEN
@@ -267,7 +221,7 @@ END PopExprDesc;
 (* proc33 @017d — ~IsSet *)
 PROCEDURE IsSet(t2, t1: RecordPtr): BOOLEAN;
 BEGIN
-  {/* 017d */} (* set-compatibility: identity, subrange unwrap, BITSET/CARD compat *)
+  (*{017d}*) (* set-compatibility: identity, subrange unwrap, BITSET/CARD compat *)
   IF (t2 = t1) THEN RETURN TRUE END;
   IF (t2^.word4 = 1) AND IsSet(t2^.word2, t1) THEN RETURN TRUE END;
   IF (t1^.word4 = 1) AND IsSet(t2, t1^.word2) THEN RETURN TRUE END;
@@ -285,7 +239,7 @@ END IsSet;
 (* proc34 @01c5 — ~BaseTypeOf *)
 PROCEDURE BaseTypeOf(t2, t1: RecordPtr): BOOLEAN;
 BEGIN
-  {/* 01c5 */} (* base-type compatibility for assignment / param passing *)
+  (*{01c5}*) (* base-type compatibility for assignment / param passing *)
   IF (t2 = t1) THEN RETURN TRUE END;
   IF (t2 = t1) OR IsSet(t2, t1) THEN RETURN TRUE END;
   IF (t2^.word4 = 1) AND (BaseTypeOf(t2^.word2, t1)) THEN RETURN TRUE END;
@@ -299,7 +253,7 @@ END BaseTypeOf;
 (* proc35 @0212 — ~FoldConstOp *)
 PROCEDURE FoldConstOp(resTyp: RecordPtr);
 BEGIN
-  {/* 0212 */} (* both sides CARDINAL/INTEGER constants: keep resTyp *)
+  (*{0212}*) (* both sides CARDINAL/INTEGER constants: keep resTyp *)
   IF (resTyp^.typ = Compiler.IntType)
      AND ((curDesc.typ = Compiler.RealType)
           OR ((curDesc.typ = Compiler.CardType) AND (resTyp^.mode = 0))
@@ -307,7 +261,7 @@ BEGIN
     curDesc.typ := resTyp^.typ;
     RETURN;
   END;
-  {/* 023a */} (* mirrored: curDesc side constant *)
+  (*{023a}*) (* mirrored: curDesc side constant *)
   IF (curDesc.typ = Compiler.IntType)
      AND ((resTyp^.typ = Compiler.RealType)
           OR ((resTyp^.typ = Compiler.CardType) AND (curDesc.mode = 0))
@@ -315,7 +269,7 @@ BEGIN
     curDesc.typ := resTyp^.typ; (* 025a..025e *)
     RETURN;
   END;
-  {/* 0260 */} (* incompatible operand types *)
+  (*{0260}*) (* incompatible operand types *)
   IF NOT IsSet(resTyp^.typ, curDesc.typ) THEN
     Errors.ReportIncompatibleTypes(resTyp^.typ, curDesc.typ, 0);
   END;
@@ -324,14 +278,14 @@ END FoldConstOp;
 (* proc7 @0275 — ~ConstToCard *)
 PROCEDURE ConstToCard(c: DescPtr; errPos: CARDINAL);
 BEGIN
-  {/* 0275 */} (* CARDINAL context: reject INTEGER/alias mismatches, negatives *)
+  (*{0275}*) (* CARDINAL context: reject INTEGER/alias mismatches, negatives *)
   IF (c^.typ <> Compiler.CardType) AND (c^.kind <> 1)
      AND (c^.value <> Compiler.CardType) THEN
     IF curDesc.mode <> 0 THEN RETURN END;
     IF curDesc.typ <> Compiler.IntType THEN RETURN END;
     IF curDesc.value < 0 THEN RETURN END;
   END;
-  {/* 029c */} (* must be set-compatible with target element type *)
+  (*{029c}*) (* must be set-compatible with target element type *)
   IF NOT IsSet(c^.typ, curDesc.typ) THEN
     Errors.ReportAssignMismatch(c^.typ, curDesc.typ, errPos);
   END;
@@ -340,7 +294,7 @@ END ConstToCard;
 (* proc8 @02ac — ~ConstToInt *)
 PROCEDURE ConstToInt(c: DescPtr; errPos: CARDINAL);
 BEGIN
-  {/* 02ac */} (* INTEGER context: base-type check only *)
+  (*{02ac}*) (* INTEGER context: base-type check only *)
   IF NOT BaseTypeOf(c^.typ, curDesc.typ) THEN
     Errors.ReportAssignMismatch(c^.typ, curDesc.typ, errPos);
   END;
@@ -349,7 +303,7 @@ END ConstToInt;
 (* proc9 @02be — ~MatchOpClass *)
 PROCEDURE MatchOpClass(allowed: BITSET; errCode: CARDINAL);
 BEGIN
-  {/* 02be */} (* operator class check on current expression type *)
+  (*{02be}*) (* operator class check on current expression type *)
   IF NOT (curDesc.typ^.word4 IN allowed) THEN
     Errors.ReportTypeMismatch(errCode, curDesc.typ, allowed);
   END;
@@ -362,10 +316,10 @@ VAR
   w8, w9: CARDINAL;        (* local-8, local-9 *)
   f1, f2: CARDINAL;        (* local-2, local-3 *)
 BEGIN
-  {/* 02d0 */} (* double PASS1.CondInsert probe for qualified pair *)
+  (*{02d0}*) (* double PASS1.CondInsert probe for qualified pair *)
   Pass1.CondInsert(probeB, ADR(w8), ADR(f1), ADR(found1));
   Pass1.CondInsert(probeA, ADR(w9), ADR(f2), ADR(found2));
-  {/* 02e6 */} (* walk while both found *)
+  (*{02e6}*) (* walk while both found *)
   WHILE found1 AND found2 DO
     IF w8 <> w9 THEN RETURN FALSE END;
     (* FIXME: LookupField internal helper — decompile from @02f4 *)
@@ -376,7 +330,7 @@ BEGIN
     Pass1.InsertEntry(probeB, ADR(w8), ADR(f1), ADR(found1));
     Pass1.InsertEntry(probeA, ADR(w9), ADR(f2), ADR(found2));
   END;
-  {/* 0324 */} (* match iff same head and same tail selector *)
+  (*{0324}*) (* match iff same head and same tail selector *)
   RETURN (found1 = found2) AND (finA = finB);
 END ParseDesignatorBase;
 
@@ -384,7 +338,7 @@ END ParseDesignatorBase;
 PROCEDURE CheckAssignable();
 VAR save: CARDINAL; (* local-2 *)
 BEGIN
-  {/* 032f */} (* void context: discard pending load, force ADDRESS-typed empty desc *)
+  (*{032f}*) (* void context: discard pending load, force ADDRESS-typed empty desc *)
   IF (curDesc.mode = 0) AND (curDesc.typ = Compiler.AddressType) THEN
     CodeGen.DiscardPending();
     curDesc.typ := Compiler.CharType;
@@ -404,11 +358,11 @@ VAR
   t: RecordPtr;      (* local-2 *)
   flags: CARDINAL;   (* local-3 *)
 BEGIN
-  {/* 0358 */} (* selector only if previous type is structured *)
+  (*{0358}*) (* selector only if previous type is structured *)
   IF sel^.kind = 11 THEN CheckAssignable() END;
   t := curDesc.typ;
   IF NOT LookupField(sel) THEN RETURN END;
-  {/* 036a */} (* address/word indirection *)
+  (*{036a}*) (* address/word indirection *)
   IF sel^.typ = Compiler.AddressType THEN
     IF LookupField(t) THEN
       flags := t^.word3;
@@ -438,7 +392,7 @@ BEGIN
     END;
     RETURN;
   END;
-  {/* 03dc */} (* set-membership / index loop *)
+  (*{03dc}*) (* set-membership / index loop *)
   WHILE 2048H IN BITSET(sel^.kind) DO
     EmitAddress() (*FIXME: dropped sel*);
     sel := sel^.word5;
@@ -446,7 +400,7 @@ BEGIN
       IF sel^.typ = curDesc.typ THEN Errors.ReportError(64) END;
     END;
   END;
-  {/* 0401 */} (* dispatch on selector class *)
+  (*{0401}*) (* dispatch on selector class *)
   IF Scanner.AcceptSy(5) THEN
     IF (sel = t) OR ((sel = Compiler.BitsetType) AND (t^.kind = 5)) THEN
     ELSIF (sel^.kind = 5) AND (t = Compiler.BitsetType) THEN

+ 11 - 0
src/compiler/PASS1.DEF

@@ -0,0 +1,11 @@
+(* PASS1 stub DEF — minimal interface for EXPRESS verification.
+   CondInsert/InsertEntry signatures proven from MCD call shapes:
+   4 pushes (value + 3 addresses), BOOLEAN use (jpfalse). Full PASS1.DEF
+   comes with the PASS1 verification pass (param names TBD). *)
+DEFINITION MODULE Pass1;
+FROM SYSTEM IMPORT ADDRESS;
+
+PROCEDURE CondInsert(flag: CARDINAL; a, b, c: ADDRESS): BOOLEAN;
+PROCEDURE InsertEntry(p1: CARDINAL; a, b, c: ADDRESS): BOOLEAN;
+
+END Pass1.

+ 227 - 201
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,134 +258,80 @@ 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*/}*)
+    REPEAT
+      e := EntryPtr(scpHeap + i * 16);
+      IF e^.w1 = CARDINAL(src) THEN (*{/*011f*/}*)
         slot := i; RETURN;
       END;
       INC(i); (*{/*012e*/}*)
-    END;
+    UNTIL i > scopeTop;
     SymbolAssert(i <= scopeCap, 86); (*{/*0137*/}*)
     scopeTop := i;
-    e := Words(scpHeap + 16 * scopeTop);
-    e^[1] := CARDINAL(src); (*{/*0141*/}*)
+    e := EntryPtr(scpHeap + scopeTop * 16);
     slot := scopeTop; (*{/*0149*/}*)
+    e^.w1 := CARDINAL(src); (*{/*0141*/}*)
     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*/}*)
+  PutAttr(e^.w2, node^.link2); (* copy link2 chain {/*016e*/} entry field direct as VAR; T1 direct *)
+  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*/}*)
+    PutAttr(e^.w5, T1(sv^.word5)); (*{/*0189*/} slot carriage: entry field; node: SRC word, not entry's *)
   | 102:
-    PutAttr(e^[5], Words(sw^[5])); (*{/*019e*/}*)
-    PutAttr(e^[6], Words(sw^[6]));
-    e^[7] := sw^[7];
+    PutAttr(e^.w5, T1(sv^.word5)); (*{/*019e*/} *)
+    PutAttr(e^.w6, T1(sv^.high));
+    e^.w7 := sw7^[7];
   ELSE (* 92..98: name/link/attr words suffice *)
   END;
 END PutAttr;
-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). *)
-VAR e: Words; new: T1; nw: Words; sub: T1;
-BEGIN
-  node := NIL; (*{/*0283*/}*)
-  IF idx = 0 THEN RETURN END; (*{/*0286*/}*)
-  e := Words(scpHeap + 16 * idx); (*{/*028a*/}*)
-  node := T1(e^[1]); (*{/*0290*/}*)
-  IF node <> NIL THEN RETURN END; (*{/*0294*/}*)
-  (* Allocator size classes exact: Scanner.NewSized(kind) =
-     14 bytes for kind <= 1, 10 for kind <= 6, 12 for kind <= 11, else 16. *)
-  new := Scanner.NewSized(e^[4]); (*{/*0299*/}*)
-  nw := Words(new);
-  e^[1] := CARDINAL(new); (* memoize {/*029e*/} *)
-  node := new; (*{/*02a1*/}*)
-  GetAttr(new^.link2, e^[2]); (* subtype {/*02a4*/} *)
-  nw^[3] := e^[3]; (*{/*02ac*/}*)
-  CASE e^[4] OF (* kind switch {/*02e1*/} *)
-  | 90, 91:
-    nw^[5] := e^[5]; nw^[6] := e^[6]; (*{/*02b4*/}*)
-  | 99, 100:
-    UnlinkScope(ADR(nw^[5]), e^[5]); (*{/*02c4*/}*)
-  | 101:
-    sub := T1(nw^[5]); GetAttr(sub, e^[5]); nw^[5] := CARDINAL(sub); (* in-place reload; VAR needs a real variable (v1.00 OOMs on CARDINAL actual) *)
-  | 102:
-    sub := T1(nw^[5]); GetAttr(sub, e^[5]); nw^[5] := CARDINAL(sub);
-    sub := T1(nw^[6]); GetAttr(sub, e^[6]); nw^[6] := CARDINAL(sub);
-    nw^[7] := e^[7];
-  ELSE (* 92..98: nothing beyond words 1..4 *)
-  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*/}*)
+  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*/}*)
-  nw^[5] := sw^[5]; nw^[6] := sw^[6]; (* dword {/*0202*/} *)
+  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(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*/} *)
+    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(sw^[5]), strHeap + strTop, 8); (*{/*0248*/}*)
+    MOVE(ADDRESS(sv^.word5), strHeap + strTop, 8); (*{/*0248*/}*)
     INC(strTop, 8); (*{/*0251*/}*)
   END;
 END AllocSym;
@@ -366,7 +341,7 @@ PROCEDURE EnterChained(scope: ADDRESS; node: T1);
 BEGIN
   IF node <> NIL THEN (*{/*025b*/}*)
     EnterChained(scope, node^.link0); (*{/*025f*/}*)
-    AllocSym(Words(scope), node); (*{/*0263*/}*)
+    AllocSym(EntryPtr(scope), node); (*{/*0263*/}*)
   END;
 END EnterChained;
 
@@ -380,6 +355,55 @@ VAR sentry: Words;          (* symHeapBase + idx*14 *)
     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). *)
+VAR e: EntryPtr; new: T1; nw: Words;
+BEGIN
+  node := NIL; (*{/*0283*/}*)
+  IF idx = 0 THEN RETURN END; (*{/*0286*/}*)
+  e := EntryPtr(scpHeap + idx * 16); (*{/*028a*/}*)
+  node := T1(e^.w1); (*{/*0290*/}*)
+  IF node <> NIL THEN RETURN END; (*{/*0294*/}*)
+  (* Allocator size classes exact: Scanner.NewSized(kind) =
+     14 bytes for kind <= 1, 10 for kind <= 6, 12 for kind <= 11, else 16. *)
+  new := Scanner.NewSized(e^.w4); (*{/*0299*/}*)
+  nw := Words(new);
+  e^.w1 := CARDINAL(new); (* memoize {/*029e*/} *)
+  node := new; (*{/*02a1*/}*)
+  GetAttr(new^.link2, e^.w2); (* subtype {/*02a4*/} *)
+  nw^[3] := CARDINAL(e^.w3); (*{/*02ac*/} BITSET field to CARDINAL slot *)
+  CASE e^.w4 OF (* kind switch {/*02e1*/} *)
+  | 90, 91:
+    nw^[5] := e^.w5; nw^[6] := e^.w6; (*{/*02b4*/}*)
+  | 99, 100:
+    UnlinkScope(ADR(nw^[5]), e^.w5); (*{/*02c4*/}*)
+  | 101:
+    new := T1(nw^[5]); GetAttr(new, e^.w5); (* tail: result discarded, memoization is the effect (original jump_stack) *)
+  | 102:
+    new := T1(nw^[5]); GetAttr(new, e^.w5);
+    new := T1(nw^[6]); GetAttr(new, e^.w6);
+    nw^[7] := e^.w7;
+  ELSE (* 92..98: nothing beyond words 1..4 *)
+  END;
+END GetAttr;
+
 BEGIN
   sentry := Words(symHeap + 14 * idx); (*{/*0307*/}*)
   new := Scanner.NewNode(sentry^[4]); (* kind; always 14 bytes {/*030d*/} *)
@@ -414,21 +438,21 @@ BEGIN
     nw^[5] := CARDINAL(nm2);
     RETURN;
   END;
-  IF sentry^[4] >= 5 THEN (*{/*037b*/}*)
-    IF rp = NIL THEN (*{/*0381*/}*)
-      Scanner.GetStack(mark); (*{/*0385*/}*)
-      nw^[6] := 0; (*{/*0389*/}*)
-      nm2 := ADDRESS(nw) + 12; (* address of nw^[6] without ADR(indexed) (expression-heap spike on v1.00) *)
-      UnlinkScope(nm2, sentry^[6]); (*{/*038c*/}*)
-      IF (sentry^[4] = 5) AND (stgFlag <> 0) THEN (*{/*0393*/} global25 *)
-        Compiler.allocHook(ADDRESS(new), mark); (* hook ADDRESS set by PASS1 (store dword27); was allocateSignature (a type symbol) — wrong target {/*039d*/} *)
-      END;
-    ELSE
-      vw := Words(rp);
-      nw^[6] := vw^[6]; (*{/*03a5*/}*)
+  IF (sentry^[4] >= 5) AND (rp = NIL) THEN (*{/*037b*/}{/*0381*/} single-depth: nested IF OOMs on v1.00 *)
+    Scanner.GetStack(mark); (*{/*0385*/}*)
+    nw^[6] := 0; (*{/*0389*/}*)
+    nm2 := ADDRESS(nw) + 12; (* address of nw^[6] without ADR(indexed) (expression-heap spike on v1.00) *)
+    UnlinkScope(nm2, sentry^[6]); (*{/*038c*/}*)
+    IF (sentry^[4] = 5) AND (stgFlag <> 0) THEN (*{/*0393*/} global25 *)
+      Compiler.allocHook(ADDRESS(new), mark); (* hook ADDRESS set by PASS1 (store dword27); was allocateSignature (a type symbol) — wrong target {/*039d*/} *)
     END;
     RETURN;
   END;
+  IF (sentry^[4] >= 5) AND (rp # NIL) THEN
+    vw := Words(rp);
+    nw^[6] := vw^[6]; (*{/*03a5*/}*)
+    RETURN;
+  END;
   IF (sentry^[4] = 1) AND (CARDINAL(new^.link2) = CARDINAL(Compiler.LongrealType)) THEN
     (* COMPILER.word11; 8-byte real value {/*03ab*/} *)
     nm2 := ADR(nw^[5]); (* temp: VAR formal rejects ADR() results (proven "Variable expected"); original pushes computed address directly *)
@@ -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*/}*)
@@ -543,16 +568,29 @@ BEGIN
   LibRead(strHeap + 1, strTop - 1); (* string pool {/*0547*/} *)
   EnterStandards; (* rewire builtin slots 1..14 {/*054f*/} *)
 END LoadSymFile;
+PROCEDURE FindExport(start: CARDINAL; name: ADDRESS): CARDINAL;
+(* proc29 @0553, nested *)
+VAR e: SymEntryPtr; idx: CARDINAL;
+BEGIN
+  idx := start;
+  WHILE idx # 0 DO
+    e := symHeap + idx * 14; (*{/*0559*/}*)
+    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*/}*)
+  END;
+  RETURN 0;
+END FindExport;
+
 PROCEDURE ImportAll(importRec: ADDRESS);
 (* proc28 @05dd *)
 VAR
   imp: ImportPtr;
-  sysImp, stoImp: ImportPtr;
-  sysAdr: ADDRESS;
-  caseFlag: BOOLEAN;    (* 9 IN scanOptions {/*05df*/} *)
-  importEvery: BOOLEAN; (* bit 10 of flags {/*05e4*/} *)
+  sysImp: ImportPtr; (* also serves storage half below (disjoint) *)
   modIdx: CARDINAL;
-  expIdx: CARDINAL;
+
   scopeIdx: CARDINAL;
   tmp: ADDRESS;
   sib: CARDINAL;
@@ -561,27 +599,29 @@ VAR
   e, e2: SymEntryPtr;
   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 *)
-  VAR e: SymEntryPtr; idx: CARDINAL;
+  PROCEDURE ImportOne(anchor: ADDRESS; symIdx: CARDINAL);
+  (* proc31 @05be, nested. EnterIdent(scope, idx) per audit (pushes (scope,idx));
+     original call order was (anchor, symIdx) — MCD diff decides. *)
+  VAR e: SymEntryPtr;
   BEGIN
-    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*/}*)
-        RETURN idx;
+    IF symIdx # 0 THEN
+      e := symHeap + symIdx * 14; (*{/*05c4*/}*)
+      ImportOne(anchor, e^.link); (*{/*05ca*/}*)
+      IF 0 IN e^.flags THEN (*{/*05d0*/}*)
+        EnterIdent(Words(anchor), symIdx); (*{/*05d6*/}*)
       END;
-      idx := e^.link; (*{/*0570*/}*)
     END;
-    RETURN 0;
-  END FindExport;
+  END ImportOne;
+
 
   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,60 +631,46 @@ 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;
   END ListExports;
 
-  PROCEDURE ImportOne(anchor: ADDRESS; symIdx: CARDINAL);
-  (* proc31 @05be, nested. EnterIdent(scope, idx) per audit (pushes (scope,idx));
-     original call order was (anchor, symIdx) — MCD diff decides. *)
-  VAR e: SymEntryPtr;
-  BEGIN
-    IF symIdx # 0 THEN
-      e := symHeap + symIdx * 14; (*{/*05c4*/}*)
-      ImportOne(anchor, e^.link); (*{/*05ca*/}*)
-      IF 0 IN e^.flags THEN (*{/*05d0*/}*)
-        EnterIdent(Words(anchor), symIdx); (*{/*05d6*/}*)
-      END;
-    END;
-  END ImportOne;
-
 BEGIN
   imp := importRec;
-  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 importEvery THEN (*{/*05f5*/}*)
+  sysImp := ADDRESS(Compiler.systemModule);
+  IF Scanner.StrCmp(imp^.name, sysImp^.name, 9 IN Scanner.scanOpt) THEN (*{/*05e9*/} *)
+    IF 10 IN imp^.flags 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 importEvery THEN (*{/*061d*/}*)
-      imp^.flags := stoImp^.flags;
-      imp^.anchor := stoImp^.anchor;
+  sysImp := ADDRESS(Compiler.storageModule);
+  IF Scanner.StrCmp(imp^.name, sysImp^.name, 9 IN Scanner.scanOpt) THEN (*{/*0611*/} *)
+    IF 10 IN imp^.flags THEN (*{/*061d*/}*)
+      imp^.flags := sysImp^.flags;
+      imp^.anchor := sysImp^.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*/}*)
-  WHILE expIdx # 0 DO (*{/*064e*/}*)
-    e := symHeap + expIdx * 14;
+  scopeIdx := expHead; (*{/*064b*/} expIdx merged into scopeIdx (disjoint, both CARDINAL loop vars) *)
+  WHILE scopeIdx # 0 DO (*{/*064e*/}*)
+    e := symHeap + scopeIdx * 14;
     IF e^.flags * BITSET{1,6} # BITSET{} THEN (* AND 42H {/*0659*/} *)
       e^.flags := e^.flags - BITSET{0}; (*{/*0663*/}*)
     ELSE
@@ -656,18 +682,18 @@ BEGIN
       e^.aux1 := e^.aux1 + (CARDINAL(CHR(modIdx)) - e^.aux1 MOD 256);
       (* byte 11 := modIdx {/*068b*/} *)
     END;
-    expIdx := e^.link; (*{/*0692*/}*)
+    scopeIdx := e^.link; (*{/*0692*/}*)
   END;
   scopeIdx := 15;
   WHILE scopeIdx <= scopeTop DO (*{/*069a*/}*)
     s := scpHeap + scopeIdx * 16;
-    matched := LookupChain(FindModule(ORD(s^.slot)), ORD(s^.fresh)); (*{/*06ab*/}*)
-    IF matched # 0 THEN (*{/*06b2*/}*)
-      s^.link := matched; s^.fresh := 0C;
+    findIdx := LookupChain(FindModule(ORD(s^.slot)), ORD(s^.fresh)); (*{/*06ab*/} matched merged into findIdx (disjoint) *)
+    IF findIdx # 0 THEN (*{/*06b2*/}*)
+      s^.link := findIdx; s^.fresh := 0C;
     END;
     INC(scopeIdx);
   END;
-  IF importEvery THEN (*{/*06c4*/}*)
+  IF 10 IN imp^.flags THEN (*{/*06c4*/}*)
     ImportOne(ADR(imp^.anchor), expHead); (* NOTE order: see ImportOne note *)
   ELSE
     REPEAT

+ 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()