فهرست منبع

m2comp step 7 — backend listing DumpImage with disassembly (65/65 tests green)

Eric Streit 3 هفته پیش
والد
کامیت
0e07513a3f
12فایلهای تغییر یافته به همراه300 افزوده شده و 6 حذف شده
  1. BIN
      M2comp
  2. 37 0
      docs/summary_m2comp_step7.md
  3. 17 2
      run_tests.sh
  4. 1 0
      src/M2comp.mod
  5. BIN
      src/M2comp.o
  6. 6 0
      src/MGen.def
  7. 225 0
      src/MGen.mod
  8. BIN
      src/MGen.o
  9. 1 0
      src/compiler.frm
  10. 1 4
      tests/d_basic.LST
  11. 9 0
      tests/d_second.LST
  12. 3 0
      tests/d_second.mod

+ 37 - 0
docs/summary_m2comp_step7.md

@@ -0,0 +1,37 @@
+# m2comp step 7 — backend listing (tag: `m2comp-step7`)
+
+`MGen.DumpImage` prints the assembled image to StdOut after every
+successful emit: global layout per scope (name/slot/size), the
+procedure table (number/address/depth), then disassembled code
+with resolved labels. Unknown bytes degrade to `DB` (never fail).
+A full sweep over all test sources shows zero unknowns. Suite
+65/65 (61 inherited + 4 listing checks).
+
+## What was built
+
+- `src/MGen.def/.mod`: `DumpImage` + helpers (`WrS/WrI/WrHex2/
+  WrHexQ/WrOp/OpLen/LabelAt`). Instruction sizes: 1, 2 (u8/ext),
+  9 (immW/jp/jpfalse); `imm0..15`, signed local slots, `loadParam`
+  for `03H..07H`; jumps resolve to `L<n>` (else `+rel`); `immW`
+  payloads as 16 hex digits.
+- Driver (`compiler.frm`): `DumpImage` after a successful
+  `EmitModule` (listings already written, verdict last).
+- `run_tests.sh`: `expect_code` (compile + grep backend listing):
+  `realCmp` in `r_real` (listing-level guard for the step-5.2 D5
+  fix), proc-table entry in `r_proc`, `jpfalse` in `t_case`,
+  `storeIndir0` in `t_with`.
+
+## Bugs found (all fixed, all covered)
+
+- `immW` printed 32 hex digits (nibble helper already emits two
+  chars each — dropped the manual split).
+- `84H/85H` leaves take a u8 param count (undocumented in the
+  local table); missing size broke alignment and swallowed a
+  label. Same class of latent defect the listing exists to catch.
+
+## Limits
+
+- No source-line mapping (AST nodes carry no positions — needs
+  grammar + AST + backend work; future step).
+- Listing goes to StdOut only (one emit per run, after the
+  per-file dumps).

+ 17 - 2
run_tests.sh

@@ -145,7 +145,8 @@ expect_fail_files() {
   fi
   fi
 }
 }
 expect_fail_multi() {
 expect_fail_multi() {
-  # stdout-only rejection (driver stage errors leave no .LST mark).
+  # stdout-only rejection: a second program trips the driver's
+  # program-last gate (both programs parse clean, so no .LST mark).
   if ./M2comp "$@" 2>&1 | grep -q "Incorrect source"; then
   if ./M2comp "$@" 2>&1 | grep -q "Incorrect source"; then
     pass=$((pass+1)); echo "PASS(fail): $*"
     pass=$((pass+1)); echo "PASS(fail): $*"
   else
   else
@@ -153,6 +154,15 @@ expect_fail_multi() {
   fi
   fi
   collect_mcd
   collect_mcd
 }
 }
+expect_code() {
+  # $1 = test file (with dir), $2 = pattern expected in backend listing.
+  if ./M2comp "$1" 2>&1 | grep -q "$2"; then
+    pass=$((pass+1)); echo "PASS(code): $1 [$2]"
+  else
+    fail=$((fail+1)); echo "FAIL(code): $1 [$2]"
+  fi
+  collect_mcd
+}
 expect_ok tests/ok_minimal.mod
 expect_ok tests/ok_minimal.mod
 expect_ok tests/ok_proc.mod
 expect_ok tests/ok_proc.mod
 expect_ok tests/showcase.mod
 expect_ok tests/showcase.mod
@@ -202,6 +212,11 @@ echo "=== Step-6 rejection tests ==="
 expect_fail tests/t_bad_with.mod "not a RECORD type"
 expect_fail tests/t_bad_with.mod "not a RECORD type"
 expect_fail tests/t_bad_case.mod "incompatible comparison"
 expect_fail tests/t_bad_case.mod "incompatible comparison"
 expect_fail tests/t_bad_caselbl.mod "not supported in this phase"
 expect_fail tests/t_bad_caselbl.mod "not supported in this phase"
+echo "=== Step-7 backend listing checks ==="
+expect_code tests/r_real.mod "realCmp"
+expect_code tests/r_proc.mod "proc.*2.*at"
+expect_code tests/t_case.mod "jpfalse"
+expect_code tests/t_with.mod "storeIndir0"
 echo "=== Step-5 run tests (DEFINITION/IMPLEMENTATION/IMPORT) ==="
 echo "=== Step-5 run tests (DEFINITION/IMPLEMENTATION/IMPORT) ==="
 expect_run_files DBasic 60 tests/d_lib.def tests/d_lib.mod tests/d_basic.mod
 expect_run_files DBasic 60 tests/d_lib.def tests/d_lib.mod tests/d_basic.mod
 expect_run_files DFrom 35 tests/d_lib.def tests/d_lib.mod tests/d_lib2.def tests/d_lib2.mod tests/d_from.mod
 expect_run_files DFrom 35 tests/d_lib.def tests/d_lib.mod tests/d_lib2.def tests/d_lib2.mod tests/d_from.mod
@@ -217,6 +232,6 @@ expect_fail_files "procedure forward mismatch" tests/d_lib.def tests/d_bad_nobod
 expect_fail_files "undeclared identifier" tests/d_lib.def tests/d_bad_noimpl.mod
 expect_fail_files "undeclared identifier" tests/d_lib.def tests/d_bad_noimpl.mod
 expect_fail_files "duplicate identifier" tests/d_lib.def tests/d_bad_dup.def
 expect_fail_files "duplicate identifier" tests/d_lib.def tests/d_bad_dup.def
 expect_fail_files "undeclared identifier" tests/d_lib.def tests/d_lib.mod tests/d_bad_priv.mod
 expect_fail_files "undeclared identifier" tests/d_lib.def tests/d_lib.mod tests/d_bad_priv.mod
-expect_fail_multi tests/d_lib.def tests/d_lib.mod tests/d_basic.mod tests/d_basic.mod
+expect_fail_multi tests/d_lib.def tests/d_lib.mod tests/d_second.mod tests/d_basic.mod
 echo "--- $pass passed, $fail failed ---"
 echo "--- $pass passed, $fail failed ---"
 [ "$fail" -eq 0 ]
 [ "$fail" -eq 0 ]

+ 1 - 0
src/M2comp.mod

@@ -437,6 +437,7 @@ MODULE M2comp;
       (* backend: emit the MC64 image (errors reach StdOut only;
       (* backend: emit the MC64 image (errors reach StdOut only;
          listings are already written) *)
          listings are already written) *)
       emitOk := MGen.EmitModule(sessionRoot);
       emitOk := MGen.EmitModule(sessionRoot);
+      IF emitOk THEN MGen.DumpImage END;
       IF NOT emitOk THEN Fail("Incorrect source") END
       IF NOT emitOk THEN Fail("Incorrect source") END
     END;
     END;
 
 

BIN
src/M2comp.o


+ 6 - 0
src/MGen.def

@@ -18,4 +18,10 @@ IMPORT AST;
 
 
 PROCEDURE EmitModule (root: AST.Node): BOOLEAN;
 PROCEDURE EmitModule (root: AST.Node): BOOLEAN;
 
 
+PROCEDURE DumpImage;
+(* Step-7 backend listing to StdOut: global layout per scope,
+   procedure table, then disassembled code with resolved labels.
+   Call once after a successful EmitModule. Unknown bytes degrade
+   to DB (never fails). *)
+
 END MGen.
 END MGen.

+ 225 - 0
src/MGen.mod

@@ -897,6 +897,231 @@ PROCEDURE EndModule;
 (* ---------------- expression lowering ---------------- *)
 (* ---------------- expression lowering ---------------- *)
 
 
 
 
+(* ---------------- backend listing (step 7) ---------------- *)
+(* Symbolic dump of the assembled image: global layout, procedure
+   table, then disassembled code with resolved labels. Unknown
+   bytes degrade to DB (never fail the listing). *)
+
+PROCEDURE WrS (s: ARRAY OF CHAR);
+  BEGIN
+    FileIO.WriteString(FileIO.StdOut, s)
+  END WrS;
+
+PROCEDURE WrI (v: INTEGER);
+  BEGIN
+    FileIO.WriteInt(FileIO.StdOut, v, 0)
+  END WrI;
+
+PROCEDURE WrHex2 (b: CARDINAL);
+  VAR hi, lo: CARDINAL;
+  BEGIN
+    hi := b DIV 16; lo := b MOD 16;
+    IF hi < 10 THEN FileIO.Write(FileIO.StdOut, CHR(ORD("0") + hi))
+    ELSE FileIO.Write(FileIO.StdOut, CHR(ORD("A") + hi - 10)) END;
+    IF lo < 10 THEN FileIO.Write(FileIO.StdOut, CHR(ORD("0") + lo))
+    ELSE FileIO.Write(FileIO.StdOut, CHR(ORD("A") + lo - 10)) END
+  END WrHex2;
+
+PROCEDURE WrHexQ (at: CARDINAL);
+(* 8 little-endian bytes as 16 hex digits (immW payloads). *)
+  VAR i: CARDINAL;
+  BEGIN
+    i := 0;
+    WHILE i < 8 DO
+      WrHex2(VAL(CARDINAL, ORD(code[at + i])));
+      INC(i)
+    END
+  END WrHexQ;
+
+PROCEDURE WrOp (b: CARDINAL);
+(* Mnemonic for a single-byte opcode; unknown -> DB hh. *)
+  BEGIN
+    CASE b OF
+      020H : WrS("dup")
+    | 021H : WrS("swap")
+    | 02CH : WrS("loadLocal")
+    | 02DH : WrS("loadGlb")
+    | 03CH : WrS("storeLocal")
+    | 03DH : WrS("storeGlb")
+    | 011H : WrS("loadOuter")
+    | 041H : WrS("loadIndir")
+    | 051H : WrS("storeIndir")
+    | 060H : WrS("loadIndir0")
+    | 070H : WrS("storeIndir0")
+    | 080H : WrS("localAddr")
+    | 081H : WrS("globalAddr")
+    | 082H : WrS("stkAddr")
+    | 0D4H : WrS("enter")
+    | 0EDH : WrS("procCall")
+    | 0ECH : WrS("nestedCall")
+    | 0EEH : WrS("callFrame")
+    | 084H : WrS("procLeave")
+    | 085H : WrS("fctLeave")
+    | 08DH : WrS("immB")
+    | 08EH : WrS("immW")
+    | 0A6H : WrS("add")
+    | 0A7H : WrS("sub")
+    | 0A8H : WrS("umul")
+    | 0A9H : WrS("udiv")
+    | 0AAH : WrS("umod")
+    | 0ABH : WrS("aeq0")
+    | 0ACH : WrS("inc")
+    | 0ADH : WrS("dec")
+    | 0A0H : WrS("eq")
+    | 0A1H : WrS("ne")
+    | 0B2H : WrS("ilt")
+    | 0B3H : WrS("igt")
+    | 0B4H : WrS("ile")
+    | 0B5H : WrS("ige")
+    | 0B6H : WrS("not")
+    | 0B8H : WrS("imul")
+    | 0B9H : WrS("idiv")
+    | 0BDH : WrS("intToLong")
+    | 0BEH : WrS("longToReal")
+    | 0D5H : WrS("realCmp")
+    | 0D6H : WrS("realAdd")
+    | 0D7H : WrS("realSub")
+    | 0D8H : WrS("realMul")
+    | 0D9H : WrS("realDiv")
+    | 0E6H : WrS("or")
+    | 0E7H : WrS("bitIn")
+    | 0E8H : WrS("and")
+    | 0E9H : WrS("bitXor")
+    | 0EAH : WrS("power2")
+    | 0E0H : WrS("jp")
+    | 0E1H : WrS("jpfalse")
+    | 0C3H : WrS("sys")
+    | 050H : WrS("end")
+    | 030H : WrS("copyBlock")
+    | 0C4H : WrS("strComp")
+    | 003H : WrS("loadParam1")
+    | 004H : WrS("loadParam2")
+    | 005H : WrS("loadParam3")
+    | 006H : WrS("loadParam4")
+    | 007H : WrS("loadParam5")
+    | 01DH : WrS("storeIdxByte")
+    | 0D2H : WrS("reserve")
+    ELSE WrS("DB "); WrHex2(b)
+    END
+  END WrOp;
+
+PROCEDURE OpLen (b: CARDINAL): CARDINAL;
+(* Instruction size: 1, 2 (u8/ext), or 9 (u64/i64). *)
+  BEGIN
+    IF (b = 02CH) OR (b = 02DH) OR (b = 03CH) OR (b = 03DH)
+       OR (b = 011H) OR (b = 080H) OR (b = 081H) OR (b = 082H)
+       OR (b = 0D4H) OR (b = 0EDH) OR (b = 0ECH) OR (b = 0EEH)
+       OR (b = 08DH) OR (b = 040H) OR (b = 084H) OR (b = 085H) THEN
+      RETURN 2
+    ELSIF (b = 08EH) OR (b = 0E0H) OR (b = 0E1H) THEN
+      RETURN 9
+    END;
+    RETURN 1
+  END OpLen;
+
+PROCEDURE LabelAt (addr: CARDINAL): INTEGER;
+(* Label id defined at addr, or -1. *)
+  VAR id: INTEGER;
+  BEGIN
+    id := 0;
+    WHILE VAL(CARDINAL, id) < nLab DO
+      IF (labs[id] >= 0) & (VAL(CARDINAL, labs[id]) = addr) THEN
+        RETURN id
+      END;
+      INC(id)
+    END;
+    RETURN -1
+  END LabelAt;
+
+PROCEDURE DumpImage;
+  VAR pc, i, j: CARDINAL;
+    b, sb, lo: CARDINAL;
+    id: INTEGER;
+    rel: INTEGER;
+  BEGIN
+    FileIO.WriteLn(FileIO.StdOut);
+    WrS("--- Code "); WrS(modName); WrS(" ---"); FileIO.WriteLn(FileIO.StdOut);
+    WrS("code "); WrI(VAL(INTEGER, nCode)); WrS(" bytes, ");
+    WrI(VAL(INTEGER, nGlb)); WrS(" global slots"); FileIO.WriteLn(FileIO.StdOut);
+    (* global layout per scope *)
+    i := 0;
+    WHILE i < nScopes DO
+      WrS("scope "); WrI(VAL(INTEGER, i)); WrS(" ");
+      WrS(scopes[i].tag); WrS(" depth "); WrI(VAL(INTEGER, scopes[i].depth));
+      FileIO.WriteLn(FileIO.StdOut);
+      j := 0;
+      WHILE j < nV DO
+        IF vars[j].scope = VAL(INTEGER, i) THEN
+          WrS("  var "); WrS(vars[j].name);
+          WrS(" slot "); WrI(vars[j].slot);
+          WrS(" size "); WrI(VAL(INTEGER, vars[j].size));
+          FileIO.WriteLn(FileIO.StdOut)
+        END;
+        INC(j)
+      END;
+      INC(i)
+    END;
+    (* procedure table *)
+    i := 1;
+    WHILE i <= maxNum DO
+      IF procAddr[i] >= 0 THEN
+        WrS("proc "); WrI(VAL(INTEGER, i));
+        WrS(" at "); WrI(procAddr[i]);
+        WrS(" depth "); WrI(VAL(INTEGER, procDepth[i]));
+        FileIO.WriteLn(FileIO.StdOut)
+      END;
+      INC(i)
+    END;
+    (* disassembly with resolved labels *)
+    pc := 0;
+    WHILE pc < nCode DO
+      id := LabelAt(pc);
+      IF id >= 0 THEN
+        WrS("L"); WrI(id); WrS(":"); FileIO.WriteLn(FileIO.StdOut)
+      END;
+      b := VAL(CARDINAL, ORD(code[pc]));
+      WrI(VAL(INTEGER, pc)); WrS("  ");
+      IF (b >= 090H) & (b <= 09FH) THEN
+        WrS("imm "); WrI(VAL(INTEGER, b - 090H)); pc := pc + 1
+      ELSIF (b = 0E0H) OR (b = 0E1H) THEN
+        IF b = 0E0H THEN WrS("jp ") ELSE WrS("jpfalse ") END;
+        (* 8-byte rel, always fits 32 bits: fold the low 4 bytes *)
+        lo := VAL(CARDINAL, ORD(code[pc + 1]))
+              + VAL(CARDINAL, ORD(code[pc + 2])) * 256
+              + VAL(CARDINAL, ORD(code[pc + 3])) * 65536
+              + VAL(CARDINAL, ORD(code[pc + 4])) * 16777216;
+        IF lo >= 2147483648 THEN
+          rel := VAL(INTEGER, lo - 2147483648) - 2147483647 - 1
+        ELSE
+          rel := VAL(INTEGER, lo)
+        END;
+        id := LabelAt(VAL(CARDINAL, VAL(INTEGER, pc) + 9 + rel));
+        IF id >= 0 THEN WrS("L"); WrI(id)
+        ELSE WrS("+"); WrI(rel)
+        END;
+        pc := pc + 9
+      ELSIF b = 08EH THEN
+        WrS("immW "); WrHexQ(pc + 1); pc := pc + 9
+      ELSIF OpLen(b) = 2 THEN
+        sb := VAL(CARDINAL, ORD(code[pc + 1]));
+        IF b = 040H THEN
+          IF sb = 0 THEN WrS("drop") ELSE WrS("ext "); WrI(VAL(INTEGER, sb)) END
+        ELSE
+          WrOp(b); WrS(" ");
+          IF ((b = 02CH) OR (b = 03CH)) & (sb > 127) THEN
+            WrI(VAL(INTEGER, sb) - 256)
+          ELSE
+            WrI(VAL(INTEGER, sb))
+          END
+        END;
+        pc := pc + 2
+      ELSE
+        WrOp(b); pc := pc + 1
+      END;
+      FileIO.WriteLn(FileIO.StdOut)
+    END
+  END DumpImage;
+
 PROCEDURE IsRealTyp (t: INTEGER): BOOLEAN;
 PROCEDURE IsRealTyp (t: INTEGER): BOOLEAN;
   BEGIN
   BEGIN
     RETURN SymTab.ClassOf(t) = SymTab.ClReal
     RETURN SymTab.ClassOf(t) = SymTab.ClReal

BIN
src/MGen.o


+ 1 - 0
src/compiler.frm

@@ -355,6 +355,7 @@ MODULE -->Grammar;
       (* backend: emit the MC64 image (errors reach StdOut only;
       (* backend: emit the MC64 image (errors reach StdOut only;
          listings are already written) *)
          listings are already written) *)
       emitOk := MGen.EmitModule(sessionRoot);
       emitOk := MGen.EmitModule(sessionRoot);
+      IF emitOk THEN MGen.DumpImage END;
       IF NOT emitOk THEN Fail("Incorrect source") END
       IF NOT emitOk THEN Fail("Incorrect source") END
     END;
     END;
 
 

+ 1 - 4
tests/d_basic.LST

@@ -2,17 +2,14 @@ Listing:
 
 
     1  MODULE DBasic;
     1  MODULE DBasic;
     2  FROM DLib IMPORT counter, Add;
     2  FROM DLib IMPORT counter, Add;
-*****                   ^ duplicate identifier
-*****                            ^ duplicate identifier
     3  IMPORT DLib;
     3  IMPORT DLib;
     4  VAR ExitCode : INTEGER;
     4  VAR ExitCode : INTEGER;
-*****      ^ duplicate identifier
     5  BEGIN
     5  BEGIN
     6    counter := 20;
     6    counter := 20;
     7    Add(5);
     7    Add(5);
     8    ExitCode := counter + DLib.Get()
     8    ExitCode := counter + DLib.Get()
     9  END DBasic.
     9  END DBasic.
 
 
-    3 errors
+    0 errors
 
 
 
 

+ 9 - 0
tests/d_second.LST

@@ -0,0 +1,9 @@
+Listing:
+
+    1  MODULE DSecond;
+    2  BEGIN
+    3  END DSecond.
+
+    0 errors
+
+

+ 3 - 0
tests/d_second.mod

@@ -0,0 +1,3 @@
+MODULE DSecond;
+BEGIN
+END DSecond.