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).
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).
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.
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).
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.