Przeglądaj źródła

m2comp step 5.2 — real-comparison codegen fix (emit D5 before stack juggling, 56/56 tests green)

Eric Streit 3 tygodni temu
rodzic
commit
92c6ad87dd

BIN
M2comp


BIN
MC4/RCmp.MC4


BIN
MC4/RReal.MC4


+ 4 - 0
docs/session_steps1-4.md

@@ -35,6 +35,10 @@ reference, this note).
   check-only. `ok_proc` fixed to terminate.
 - Negative-vs-zero `D5` quirk reproduces byte-identically under
   m2c — VM domain, avoided in tests.
+  [Correction (step 5.2): the VM was innocent. Real `< > <= >=`
+  never emitted `D5` — bare stack juggling left operand bits as
+  the boolean (nonzero reads back TRUE). Fixed in both MGens;
+  see `summary_m2comp_step5.2.md`.]
 
 ## Tomorrow: step 5 candidates (in priority order)
 

+ 5 - 0
docs/summary_m2comp_step4.md

@@ -90,6 +90,11 @@ const-chains 1102.
 - Negative-vs-zero `D5` real comparisons misbehave identically
   under byte-identical m2c output — VM-domain quirk, avoided in
   tests, not a backend bug.
+  [Correction (step 5.2): misattributed — it WAS a backend
+  (codegen) bug in the recipe shared by both compilers: real
+  `< > <= >=` emitted stack juggling with no `D5`, so a leftover
+  operand's bits became the boolean. `D5` itself was always
+  correct. Fixed; see `summary_m2comp_step5.2.md`.]
 
 ## Deviations from m2c (documented)
 

+ 44 - 0
docs/summary_m2comp_step5.2.md

@@ -0,0 +1,44 @@
+# m2comp step 5.2 — real-comparison codegen fix (tag: `m2comp-step5.2`)
+
+The step-4 "VM-domain `D5` quirk" (negative-vs-zero real
+comparisons) re-checked: the VM was innocent. Real `< > <= >=`
+never emitted `D5` (`0D5H`) — only `=` / `#` did. The ordered
+comparisons emitted bare stack juggling, leaving a raw operand
+bit-pattern as the boolean (nonzero reads back TRUE). Fixed in
+both compilers' MGens; `D5` itself needed no change (`mcint` and
+all images untouched). Suite 56/56 (new `r_rcmp` regression).
+
+## Reproduction
+
+13-case probe (`tests/r_rcmp.mod`, powers-of-two encoding):
+`-1.0</>/= 0.0`, `0.0</>/= -1.0`, `-1.0</>/= -1.0`, `0.0 = 0.0`,
+`-0.0</>/= 0.0` (via `-1.0 * 0.0`). Before: five `<`/`>` results
+wrong (every `<` behaved per the wrong operand, every `>` likewise;
+`=` correct throughout, including `-0.0 = 0.0`). After: 31
+(all 13 correct).
+
+## Root cause
+
+`EmitExpr` lowered real `</>/<=/>=` to `swap`/`drop`/`not`
+sequences that are exactly right *for `D5` outputs on the stack*
+— but never emitted the `D5`. So `<` yielded `(right-operand ≠ 0)`,
+`>` yielded `(left-operand ≠ 0)`, etc. It hid because most tests
+compare nonzero values. v1's `MGen` even documented the broken
+contract (`RealLt: [gt lt] -> lt` — inputs no caller provides);
+V2 had copied the recipe inline. Both compilers emitted identical
+bytes, hence the "reproduces under m2c" misdirection.
+
+## Fix
+
+One `OPrCmp` prepended per sequence (contracts now `[r1 r2]`):
+`<` = cmp,swap,drop (keep `lt`); `<=` = cmp,drop,not; `>`
+= cmp,drop (keep `gt`); `>=` = cmp,swap,drop,not. Same four lines
+in `m2compiler-v1/src/MGen.mod` (`RealLt/Le/Gt/Ge`, comments
+corrected); `r_real`/`t_real` expectations needed no change (they
+were written for correct semantics and now pass genuinely).
+
+## Note corrections
+
+Step-4's "Non-bugs established by bisection" entry and the
+steps 1–4 wrap-up's "Hard-won facts" entry blamed the VM; both
+now carry a `[Correction (step 5.2)]` annotation pointing here.

+ 1 - 0
run_tests.sh

@@ -181,6 +181,7 @@ expect_run r_string.mod 111
 expect_run r_ptr.mod 111
 expect_run r_module.mod 83
 expect_run r_const.mod 1102
+expect_run r_rcmp.mod 31
 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 DFrom 35 tests/d_lib.def tests/d_lib.mod tests/d_lib2.def tests/d_lib2.mod tests/d_from.mod

+ 5 - 4
src/MGen.mod

@@ -1173,21 +1173,22 @@ PROCEDURE EmitExpr (n: AST.Node);
         END
       ELSIF n^.op = SymTab.OpLt THEN
         IF isR THEN
-          EmitOp(OPswap); EmitOp(OPext); EmitOp(SUBdrop)
+          EmitOp(OPrCmp); EmitOp(OPswap); EmitOp(OPext); EmitOp(SUBdrop)
         ELSE EmitOp(OPilt)
         END
       ELSIF n^.op = SymTab.OpLe THEN
         IF isR THEN
-          EmitOp(OPext); EmitOp(SUBdrop); EmitOp(OPnot)
+          EmitOp(OPrCmp); EmitOp(OPext); EmitOp(SUBdrop); EmitOp(OPnot)
         ELSE EmitOp(OPile)
         END
       ELSIF n^.op = SymTab.OpGt THEN
-        IF isR THEN EmitOp(OPext); EmitOp(SUBdrop)
+        IF isR THEN
+          EmitOp(OPrCmp); EmitOp(OPext); EmitOp(SUBdrop)
         ELSE EmitOp(OPigt)
         END
       ELSIF n^.op = SymTab.OpGe THEN
         IF isR THEN
-          EmitOp(OPswap); EmitOp(OPext); EmitOp(SUBdrop);
+          EmitOp(OPrCmp); EmitOp(OPswap); EmitOp(OPext); EmitOp(SUBdrop);
           EmitOp(OPnot)
         ELSE EmitOp(OPige)
         END

BIN
src/MGen.o


+ 37 - 0
tests/r_rcmp.LST

@@ -0,0 +1,37 @@
+Listing:
+
+    1  MODULE RCmp;
+    2  (* Real comparison regression test: < > <= >= must emit D5
+    3     (0D5H); they once emitted bare stack juggling, leaving operand
+    4     bits as the boolean (negative-vs-zero failures). ExitCode = 31:
+    5     bits 0-4 for the five true comparisons, none of the 64..4096
+    6     false-positive bits. *)
+    7  VAR ExitCode : INTEGER;
+    8      a, b, c, d, e, f, g, h, i, j, k, l : REAL;
+    9  BEGIN
+   10    a := -1.0; b := 0.0;
+   11    c := 0.0; d := -1.0;
+   12    e := -1.0; f := -1.0;
+   13    g := 0.0; h := 0.0;
+   14    i := -1.0; j := 0.0;
+   15    k := i * j;
+   16    l := 0.0;
+   17    ExitCode := 0;
+   18    IF a < b THEN ExitCode := ExitCode + 1 END;
+   19    IF c > d THEN ExitCode := ExitCode + 2 END;
+   20    IF e = f THEN ExitCode := ExitCode + 4 END;
+   21    IF g = h THEN ExitCode := ExitCode + 8 END;
+   22    IF k = l THEN ExitCode := ExitCode + 16 END;
+   23    IF a > b THEN ExitCode := ExitCode + 32 END;
+   24    IF a = b THEN ExitCode := ExitCode + 64 END;
+   25    IF c < d THEN ExitCode := ExitCode + 128 END;
+   26    IF c = d THEN ExitCode := ExitCode + 256 END;
+   27    IF e < f THEN ExitCode := ExitCode + 512 END;
+   28    IF e > f THEN ExitCode := ExitCode + 1024 END;
+   29    IF k < l THEN ExitCode := ExitCode + 2048 END;
+   30    IF k > l THEN ExitCode := ExitCode + 4096 END
+   31  END RCmp.
+
+    0 errors
+
+

+ 31 - 0
tests/r_rcmp.mod

@@ -0,0 +1,31 @@
+MODULE RCmp;
+(* Real comparison regression test: < > <= >= must emit D5
+   (0D5H); they once emitted bare stack juggling, leaving operand
+   bits as the boolean (negative-vs-zero failures). ExitCode = 31:
+   bits 0-4 for the five true comparisons, none of the 64..4096
+   false-positive bits. *)
+VAR ExitCode : INTEGER;
+    a, b, c, d, e, f, g, h, i, j, k, l : REAL;
+BEGIN
+  a := -1.0; b := 0.0;
+  c := 0.0; d := -1.0;
+  e := -1.0; f := -1.0;
+  g := 0.0; h := 0.0;
+  i := -1.0; j := 0.0;
+  k := i * j;
+  l := 0.0;
+  ExitCode := 0;
+  IF a < b THEN ExitCode := ExitCode + 1 END;
+  IF c > d THEN ExitCode := ExitCode + 2 END;
+  IF e = f THEN ExitCode := ExitCode + 4 END;
+  IF g = h THEN ExitCode := ExitCode + 8 END;
+  IF k = l THEN ExitCode := ExitCode + 16 END;
+  IF a > b THEN ExitCode := ExitCode + 32 END;
+  IF a = b THEN ExitCode := ExitCode + 64 END;
+  IF c < d THEN ExitCode := ExitCode + 128 END;
+  IF c = d THEN ExitCode := ExitCode + 256 END;
+  IF e < f THEN ExitCode := ExitCode + 512 END;
+  IF e > f THEN ExitCode := ExitCode + 1024 END;
+  IF k < l THEN ExitCode := ExitCode + 2048 END;
+  IF k > l THEN ExitCode := ExitCode + 4096 END
+END RCmp.