Kaynağa Gözat

runtime: MBF comparison and Int (recmp, reint)

Two new entries, both direct TPSRC3 transcriptions:

  * real_cmp (entry 26, recmp).  [BP+4] is the arithmetic block (a then
    b); nothing is written; AX := -1/0/+1, the same convention cmpstr
    returns, so the compiler keeps one comparison arm for reals and
    strings.  The reference's flag-setting core is intact: different signs
    are decided by RCL on a's high word, and two negatives invert the
    magnitude result.  Thirty rt_exec cases.

  * real_int (entry 27, reint).  [BP+4] is the real, transformed in place.
    Int(real) is a REAL, not an integer: the result stays six bytes.  The
    mask-and-AND works on the packed bytes directly (fraction cleared,
    sign bit and implicit leading 1 kept); exponents >= 0A8h are already
    integral, <= 80h give zero.  Eighteen cases, incl. 12345.75 -> 12345.

The harness can now observe a return value: rtdrv.s stores every call's
AX at D_AX (0x0AA6), and a case can dump that word.  Until now cmpstr's
sign was only pinned by whole programs.

Runtime 1833 -> 2059 bytes, 28 entries.  rt_exec 191/191.  The golden is
re-blessed and the audit covers the new emitters.  Non-vacuity 113/113:
XorDxDi -> XorDxSi and the 0A8h cutoff -> 088h must each turn a real case
red.  run_all ALL PASS.

Headroom: RT_BASE 0x190 and HDR 0x0A00 cap the runtime at 2160 bytes; the
next real rung will need another scaffold shift.
Eric Streit 1 gün önce
ebeveyn
işleme
3409f8aac5

+ 31 - 8
CALC-COMPILE.md

@@ -552,11 +552,11 @@ Exec86):
   **OVERALL: ALL PASS**, compile matrix **83/83**, CALC ladder **1/1** at
   `ERR 102 18267`, terminal config **4/4**, 64 `.COM` linked and **72 executed
   under both qemu and Exec86** (`t64_arrays` is compile-only: its image is past
-  the 8 KB boot-sector read).  `tests/nonvacuity.sh`: **111 ok, 0 failed**.
-  The runtime is 1833 bytes (wrintw, real_from_int, real_add, real_sub,
-  real_mul, real_div; the golden was re-blessed) and `tests/rt_exec.py`
-  passes **143/143**, including 16 add/sub, ten multiply and fourteen
-  division exact-byte cases.
+  the 8 KB boot-sector read).  `tests/nonvacuity.sh`: **113 ok, 0 failed**.
+  The runtime is 2059 bytes (wrintw, real_from_int, real_add, real_sub,
+  real_mul, real_div, real_cmp, real_int; the golden was re-blessed) and
+  `tests/rt_exec.py` passes **191/191**, including 16 add/sub, ten multiply,
+  fourteen division, thirty comparison and eighteen Int exact-byte cases.
   `check_realconst.py` pins the measured 6-byte Real operand bytes.
 - **`tests/nonvacuity.sh`:** re-run after this commit's cases (the set
   membership bit among them); see the commit message for the count. It is not in
@@ -701,9 +701,32 @@ Then the two that are *outside* the ladder and gate the goal as stated:
             normal compare path has CF=0, which hid it in 13 of 14 cases.
           - the runtime outgrew the probe scaffold: rtdrv.s/rt_exec.py moved
             their records and blocks up 0x200 (HDR=0x0A00, blocks 0x0B30+).
-      * **NEXT:** `recmp` (the six real comparisons), `reint`, then
-        `fmtreal`/`ascreal` for `Write(Value:FW:DEC)`, `Str` and `Val`.
-        Each is a direct transcription with rt_exec cases.
+      * **DONE: `real_cmp`** (`v-TP3-MBF-CMPINT`, entry 26).  TPSRC3 `recmp`.
+        ABI: [BP+4] is the same 12-byte block as the arithmetic entries;
+        nothing is written; the result is AX = -1/0/+1, cmpstr's convention,
+        so the compiler has ONE comparison arm for reals and strings.  The
+        reference's core keeps the flags; the last step is the untested
+        flag-to-AX conversion.  Different signs are decided by RCL on a's
+        high word (the sign lands in CF); two negatives invert the magnitude
+        result with CMC.  Thirty rt_exec cases.
+        The harness gained an observable AX: rtdrv.s stores every call's AX
+        at D_AX (0x0AA6), so a case can dump it.  Until now cmpstr's return
+        sign was only checked by whole programs.
+      * **DONE: `real_int`** (`v-TP3-MBF-CMPINT`, entry 27).  TPSRC3 `reint`,
+        in place at [BP+4].  It is NOT integer-valued: `Int(real)` returns a
+        REAL, so the result stays six bytes.  The reference works directly on
+        the packed bytes (no hidden-bit setup): it builds a mask by shifting
+        1s right by `exponent - 129` and ANDs the mantissa, which clears the
+        fraction and leaves the sign bit and the implicit leading 1 alone.
+        Exponents >= 0A8h (2^39) are already integral and return unchanged;
+        <= 80h give zero.  Eighteen rt_exec cases, including 12345.75 -> 12345
+        and 2^40 unchanged.
+      * **NEXT:** `fmtreal`/`ascreal` for `Write(Value:FW:DEC)`, `Str` and
+        `Val`.  Each is a direct transcription with rt_exec cases.
+      * **HEADROOM:** the runtime is 2059 bytes and the probe's scaffold now
+        starts at HDR=0x0A00 with RT_BASE=0x0190, so the ceiling is 2160
+        bytes - about 100 bytes left.  The next real rung will need another
+        shift of rtdrv.s/rt_exec.py, or a larger probe image.
       * **THEN** the compiler side: real expressions need a value model
         (pointers to 6-byte temporaries), real comparisons, and real function
         results (currently refused because the result is wider than a word).

+ 2 - 0
shell/Runtime.def

@@ -58,6 +58,8 @@ CONST
    E_RealSub = 23 ;         (* ...and subtraction: a - b *)
    E_RealMul = 24 ;         (* ...and multiplication: a * b *)
    E_RealDiv = 25 ;         (* ...and division: a / b *)
+   E_RealCmp = 26 ;         (* real comparison: AX = -1/0/+1 for a<b, a=b, a>b *)
+   E_RealInt = 27 ;         (* Int(real): truncate the mantissa in place *)
 
    (* Where a .COM's first byte ends up.  DOS loads a .COM at CS:0100 (the
       100 bytes below are the PSP), and CS = DS, so an image offset K is at

+ 132 - 2
shell/Runtime.mod

@@ -139,7 +139,7 @@ VAR
    dataAt : CARDINAL ;
    rtBase : CARDINAL ;
    built  : BOOLEAN ;
-   entNm  : ARRAY [0..25] OF ARRAY [0..MaxNm] OF CHAR ;
+   entNm  : ARRAY [0..27] OF ARRAY [0..MaxNm] OF CHAR ;
 
 (* ---------------------------------------------------------------- *)
 (*  name helpers                                                     *)
@@ -648,7 +648,16 @@ PROCEDURE RclCh1 ; BEGIN B (0D0H) ; B (0D5H) END RclCh1 ;   (* RCL CH,1 *)
 PROCEDURE Lahf ; BEGIN B (9FH) END Lahf ;
 PROCEDURE Sahf ; BEGIN B (9EH) END Sahf ;
 PROCEDURE XorCxDi ; BEGIN B (31H) ; B (0F9H) END XorCxDi ;   (* CX:=CX XOR DI *)
+PROCEDURE XorDxDi ; BEGIN B (31H) ; B (0FAH) END XorDxDi ;   (* DX:=DX XOR DI *)
 PROCEDURE NotCh ; BEGIN B (0F6H) ; B (0D5H) END NotCh ;     (* NOT CH *)
+PROCEDURE AndDxDi ; BEGIN B (21H) ; B (0FAH) END AndDxDi ;   (* AND DX,DI *)
+PROCEDURE AndBxSi ; BEGIN B (21H) ; B (0F3H) END AndBxSi ;   (* AND BX,SI *)
+PROCEDURE AndAhCh ; BEGIN B (20H) ; B (0ECH) END AndAhCh ;   (* AND AH,CH *)
+PROCEDURE CmpDxDi ; BEGIN B (39H) ; B (0FAH) END CmpDxDi ;   (* CMP DX,DI *)
+PROCEDURE CmpBxSi ; BEGIN B (39H) ; B (0F3H) END CmpBxSi ;   (* CMP BX,SI *)
+PROCEDURE CmpAhCh ; BEGIN B (38H) ; B (0ECH) END CmpAhCh ;   (* CMP AH,CH *)
+PROCEDURE MovDhImm (v : CARDINAL) ;
+BEGIN B (0B6H) ; B (v MOD 256) END MovDhImm ;                (* MOV DH,imm8 *)
 PROCEDURE AndChImm (v : CARDINAL) ;
 BEGIN B (80H) ; B (0E5H) ; B (v MOD 256) END AndChImm ;
 PROCEDURE MovDiData (delta : CARDINAL) ;
@@ -1985,6 +1994,124 @@ BEGIN
    JmpNear ("rm_noadj")
 END EmitRealMul ;
 
+PROCEDURE EmitRealCmp ;
+(* recmp: compare two packed 6-byte MBF reals.
+
+   ABI: [BP+4] points at a 12-byte block holding a then b (the same block the
+   four arithmetic entries take).  Nothing is written; the result is AX =
+   -1/0/+1 for a<b, a=b, a>b.
+
+   TP3's recmp leaves the flags set and its six wrappers branch on them.  Here
+   the caller is our compiler, which already has one comparison arm for reals
+   and strings: EmSetcc over AX.  cmpstr returns the same -1/0/+1, so the two
+   cannot drift.  The reference's core is kept intact; only the final
+   flag-to-AX step is new, and it is cmpstr's: MOV AX,0 leaves the flags alone,
+   then JB/JE/MOV decide.
+
+   The comparison is on the packed representation: the sign is bit 7 of each
+   high byte, the magnitude is exponent then DX/BX/AH (TP3's 40-bit order), and
+   a negative pair inverts the magnitude result.  Different signs are decided
+   by RCL on a's high word, which shifts the sign into CF - exactly TPSRC's
+   trick, and it leaves ZF clear because the XOR found the signs different. *)
+BEGIN
+   M ("real_cmp") ;
+   PushBp ; MovBpSp ;
+   PushBx ; PushDx ; PushSi ; PushDi ;
+   MovSiBp4 ;
+   LdAxSi ;
+   LdCxSiOff (2) ; LdDxSiOff (4) ;
+   MovBxCx ;
+   MovDiSi ;
+   LdCxDiOff (6) ; LdSiDiOff (8) ; LdDiDiOff (10) ;
+   (* recmp core *)
+   PushDx ; XorDxDi ; PopDx ;
+   Jns8 ("rc_sign") ;
+   PushDx ; RclDx1 ; PopDx ;
+   Jmp8 ("rc_flags") ;
+   M ("rc_sign") ;
+   TestDhImm (80H) ;
+   Je8 ("rc_pos") ;
+   Call ("rc_num") ;
+   Je8 ("rc_flags") ;
+   Cmc ;
+   Jmp8 ("rc_flags") ;
+   M ("rc_pos") ;
+   Call ("rc_num") ;
+   M ("rc_flags") ;
+   MovAlD (0) ; MovAh (0) ;          (* AX := 0; MOV leaves the flags alone *)
+   Jb8 ("rc_less") ;
+   Je8 ("rc_done") ;
+   MovAlD (1) ;
+   Jmp8 ("rc_done") ;
+   M ("rc_less") ;
+   MovAlD (0FFH) ; MovAh (0FFH) ;
+   M ("rc_done") ;
+   PopDi ; PopSi ; PopDx ; PopBx ;
+   MovSpBp ; PopBp ; RetR ;
+   (* the shared magnitude compare; CALL/RET do not touch the flags *)
+   M ("rc_num") ;
+   CmpAlCl ; Jne8 ("rc_num_ret") ;
+   CmpAl0 ; Je8 ("rc_num_ret") ;
+   CmpDxDi ; Jne8 ("rc_num_ret") ;
+   CmpBxSi ; Jne8 ("rc_num_ret") ;
+   CmpAhCh ;
+   M ("rc_num_ret") ;
+   RetR
+END EmitRealCmp ;
+
+PROCEDURE EmitRealInt ;
+(* reint: Int(real), the real-valued integer part, in place.
+
+   ABI: [BP+4] is the address of a 6-byte packed MBF real; the result
+   overwrites it.  TPSRC3's reint works directly on the packed bytes: it builds
+   a mask by shifting 1s right by (exponent - 129) bits and ANDs it with the
+   mantissa, which clears the fraction while keeping the sign bit and the
+   implicit leading 1.  Exponents >= 0A8h are already integral and returned
+   unchanged; exponents <= 80h give zero.
+
+   The 16/8-bit fast paths are the reference's, including the byte moves that
+   shift the mask instead of running the bit loop.  Only the wrapper is ours
+   (the caller passes an address rather than the value in registers). *)
+BEGIN
+   M ("real_int") ;
+   PushBp ; MovBpSp ;
+   PushBx ; PushDx ; PushSi ; PushDi ;
+   PushBp4 ;
+   MovSiBp4 ;
+   LdAxSi ;
+   LdCxSiOff (2) ; LdDxSiOff (4) ;
+   MovBxCx ;
+   (* reint core *)
+   CmpAl (0A8H) ;
+   Jae8 ("ri_store") ;               (* already integral: return as-is *)
+   MovCxAx ; MovSiBx ; MovDiDx ;     (* save the packed mantissa for the mask *)
+   XorAhAh ; XorBxBx ; XorDxDx ;     (* build the mask in AH/BX/DX *)
+   SubClImm (80H) ;                  (* CL := exponent - 129 + 1 *)
+   Jbe8 ("ri_zero") ;
+   M ("ri_16") ;
+   CmpClImm (10H) ; Jb8 ("ri_8") ;
+   MovAhBh ; MovBxDx ; MovDxImm (0FFFFH) ;
+   SubClImm (10H) ; Jmp8 ("ri_16") ;
+   M ("ri_8") ;
+   CmpClImm (08H) ; Jb8 ("ri_1") ;
+   MovAhBl ; MovBlBh ; MovBhDl ; MovDlDh ; MovDhImm (0FFH) ;
+   SubClImm (08H) ; Jmp8 ("ri_8") ;
+   M ("ri_1") ;
+   CmpCl0 ; Je8 ("ri_and") ;
+   M ("ri_1l") ;
+   Stc ; RcrDx1 ; RcrBx1 ; RcrAh1 ; DecCl ; Jne8 ("ri_1l") ;
+   M ("ri_and") ;
+   AndDxDi ; AndBxSi ; AndAhCh ;
+   M ("ri_store") ;
+   PopDi ;
+   StDiAx ; StDiOffBx (2) ; StDiOffDx (4) ;
+   PopDi ; PopSi ; PopDx ; PopBx ;
+   MovSpBp ; PopBp ; RetR ;
+   M ("ri_zero") ;
+   MovAlD (0) ;
+   Jmp8 ("ri_store")
+END EmitRealInt ;
+
 PROCEDURE EmitData ;
 (* The runtime's data block.  The D_ constants name the offsets in it and are
    ASSERTED against what is emitted here, three ways: the starting offset of
@@ -2126,6 +2253,7 @@ BEGIN
    EmitGetCh ; EmitUnGetCh ;
    EmitRdStr ; EmitStrStore ; EmitCmpStr ; EmitStrCat ;
    EmitRealFromInt ; EmitRealAddSub ; EmitRealMul ;
+   EmitRealCmp ; EmitRealInt ;
    EmitData ;
    FixUp ;
    SetStr (entNm [0], "initmem") ;
@@ -2154,6 +2282,8 @@ BEGIN
    SetStr (entNm [23], "real_sub") ;
    SetStr (entNm [24], "real_mul") ;
    SetStr (entNm [25], "real_div") ;
+   SetStr (entNm [26], "real_cmp") ;
+   SetStr (entNm [27], "real_int") ;
    built := TRUE
 END RT_Build ;
 
@@ -2181,7 +2311,7 @@ BEGIN
    IF NOT built THEN
       RT_Build (0)
    END ;
-   IF i > 25 THEN
+   IF i > 27 THEN
       RETURN 0
    END ;
    RETURN rtBase + LblOff (entNm [i])

+ 5 - 3
shell/tests/RtProbe.mod

@@ -108,7 +108,7 @@ END PENT ;
 
 PROCEDURE Main ;
 VAR i, n, base : CARDINAL ;
-    names : ARRAY [0..25] OF ARRAY [0..15] OF CHAR ;
+    names : ARRAY [0..27] OF ARRAY [0..15] OF CHAR ;
 BEGIN
    base := GetBase () ;
    RT_Build (base) ;
@@ -121,7 +121,7 @@ BEGIN
    PCARD (RT_Size ()) ; PS (" bytes") ; NL ;
    PS ("code ends at ") ; PCARD (RT_CodeEnd ()) ; NL ;
    i := 0 ;
-   WHILE i <= 25 DO
+   WHILE i <= 27 DO
       names [i] := "" ;
       INC (i)
    END ;
@@ -138,8 +138,10 @@ BEGIN
    names [23] := "real_sub" ;
    names [24] := "real_mul" ;
    names [25] := "real_div" ;
+   names [26] := "real_cmp" ;
+   names [27] := "real_int" ;
    i := 0 ;
-   WHILE i <= 25 DO
+   WHILE i <= 27 DO
       PENT (i, names [i]) ;
       INC (i)
    END ;

+ 1 - 0
shell/tests/audit_helpers.py

@@ -134,6 +134,7 @@ NON_ONE_LINE = {
         "SubAhData": COMP, "StDataAl": COMP,
         "MovBxImm": PARAM, "MovCxImm": PARAM, "MovDxImm": PARAM,
         "MovDl": PARAM, "MovAlD": PARAM, "MovAh": PARAM,
+        "MovDhImm": PARAM,
         "SubAl": PARAM, "CmpAl": PARAM, "AddDl": PARAM, "J8": COMP,
         "Jcc": COMP, "CmpCxV": PARAM,
         "B": DATA, "C8": DATA,

+ 2 - 0
shell/tests/check_runtime.py

@@ -137,6 +137,8 @@ GOLDEN = {
     "real_sub": "55 8B EC 53 52 56 57",
     "real_mul": "55 8B EC 53 52 56 57",
     "real_div": "55 8B EC 53 52 56 57",
+    "real_cmp": "55 8B EC 53 52 56 57",
+    "real_int": "55 8B EC 53 52 56 57",
     # wrchar: AL, not DL.  DOS INT 21h AH=02h takes the character in AL, and
     # DL is the *other* 8086 convention (BIOS teletype).  This read DL, so
     # every write of a character printed whatever the *last* character read

+ 39 - 0
shell/tests/exec/rtdrv.s

@@ -102,6 +102,7 @@
 	.set	D_DUMPLEN, 0x0AA0	# the driver's own three words
 	.set	D_DUMPADR, 0x0AA2
 	.set	D_CNT, 0x0AA4
+	.set	D_AX, 0x0AA6		# last call's AX, for the comparison cases
 	.set	RDSTR_DST, 0x0AB0	# destination for the rdstr cases
 	.set	SST_HELLO, 0x0AC0	# a short source string for strstore
 	.set	SST_LONG, 0x0AD0	# a source longer than a small max
@@ -132,6 +133,17 @@
 	.set	R_BLK18, 0x0C40
 	.set	R_BLK19, 0x0C50
 	.set	R_BLK20, 0x0C60
+	.set	R_BLK21, 0x0C70
+	.set	R_BLK22, 0x0C80
+	.set	R_BLK23, 0x0C90
+	.set	R_BLK24, 0x0CA0
+	.set	R_BLK25, 0x0CB0
+	.set	R_BLK26, 0x0CC0
+	.set	R_BLK27, 0x0CD0
+	.set	R_BLK28, 0x0CE0
+	.set	R_BLK29, 0x0CF0
+	.set	R_BLK30, 0x0D00
+	.set	R_BLK31, 0x0D10
 
 	.code16
 	.text
@@ -273,6 +285,7 @@ doslot:
 	testb	$4, %al
 	jnz	.Linl
 	call	*%si
+	movw	%ax, D_AX + LOAD_BIAS	# observable return value
 	movb	4(%bp), %al
 	testb	$8, %al
 	jnz	.Lcleantwo
@@ -475,3 +488,29 @@ HDR:
 	.byte	0x83, 0x00, 0x00, 0x00, 0x00, 0x20, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00
 	.org	R_BLK20
 	.byte	0x86, 0x00, 0x00, 0x00, 0x00, 0x28, 0x83, 0x00, 0x00, 0x00, 0x00, 0x40
+
+# Fractional blocks: real_int truncates `a'; real_cmp compares the pair.
+	.org	R_BLK21
+	.byte	0x82, 0xCD, 0xCC, 0xCC, 0xCC, 0x6C, 0x82, 0xCD, 0xCC, 0xCC, 0xCC, 0x6C
+	.org	R_BLK22
+	.byte	0x82, 0x9A, 0x99, 0x99, 0x99, 0x79, 0x82, 0xCD, 0xCC, 0xCC, 0xCC, 0x6C
+	.org	R_BLK23
+	.byte	0x82, 0xCD, 0xCC, 0xCC, 0xCC, 0xEC, 0x82, 0x9A, 0x99, 0x99, 0x99, 0xF9
+	.org	R_BLK24
+	.byte	0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00
+	.org	R_BLK25
+	.byte	0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00
+	.org	R_BLK26
+	.byte	0x8E, 0x00, 0x00, 0x00, 0xE7, 0x40, 0x8E, 0x00, 0x00, 0x00, 0xE6, 0x40
+	.org	R_BLK27
+	.byte	0x82, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+	.org	R_BLK28
+	.byte	0x82, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+	.org	R_BLK29
+	.byte	0x80, 0x0A, 0xD7, 0xA3, 0x70, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+	.org	R_BLK30
+	.byte	0x81, 0x33, 0x33, 0x33, 0x33, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+# 2^40: above reint's 0A8h early return, so it must come back unchanged.
+	.org	R_BLK31
+	.byte	0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

+ 33 - 0
shell/tests/nonvacuity.sh

@@ -1441,6 +1441,39 @@ fi
 cp "$SAVED" Runtime.mod
 "$GM2" -fiso -c Runtime.mod >/dev/null 2>&1
 
+# --- recmp compares b's SIGN, not its middle word -----------------------
+# The different-sign path is decided by RCL on a's high word, but WHICH pair is
+# different is decided by XOR DX,DI.  XORing with SI (b's middle mantissa word)
+# still sees a's sign, so two negatives are taken as different signs: -3.7 and
+# -3.9 then compare backwards.  The fractional real_cmp cases catch it.
+if mutate Runtime.mod 's|BEGIN B (31H) ; B (0FAH) END XorDxDi|BEGIN B (31H) ; B (0F2H) END XorDxDi|'; then
+    if "$GM2" -fiso -c Runtime.mod >/dev/null 2>&1; then
+        expect_red "real_cmp red when the sign XOR uses SI instead of DI" \
+            "real_cmp" python3 tests/rt_exec.py
+    else
+        echo "  FAIL: the runtime would not rebuild with the XorDxDi mutation"
+        fail=$((fail + 1))
+    fi
+fi
+cp "$SAVED" Runtime.mod
+"$GM2" -fiso -c Runtime.mod >/dev/null 2>&1
+
+# --- reint must not skip the mask for ordinary large reals ---------------
+# 0A8h is 2^39, above which every representable value is already integral.
+# Lowering it to 088h (256) makes 12345.75 come back unchanged instead of
+# 12345: the fraction survives, and the exact-byte real_int case says so.
+if mutate Runtime.mod 's|CmpAl (0A8H)|CmpAl (088H)|'; then
+    if "$GM2" -fiso -c Runtime.mod >/dev/null 2>&1; then
+        expect_red "real_int red when the integral short-circuit starts too low" \
+            "real_int" python3 tests/rt_exec.py
+    else
+        echo "  FAIL: the runtime would not rebuild with the CmpAl mutation"
+        fail=$((fail + 1))
+    fi
+fi
+cp "$SAVED" Runtime.mod
+"$GM2" -fiso -c Runtime.mod >/dev/null 2>&1
+
 # --- the compiler's arms --------------------------------------------------
 # The limit both entries read is MOV CX,imm16.  MOV AX,imm16 is one opcode away,
 # the same length, and the audit's MovCxi row is COMP - "no single sequence to

+ 56 - 1
shell/tests/rt_exec.py

@@ -42,6 +42,7 @@ readln that printed anything would change the same compared string, so the
 assertion is unchanged - one qemu boot per check instead of two.
 """
 
+import math
 import os
 import re
 import struct
@@ -78,6 +79,7 @@ SST_DST = 0x0AF0
 SST_CAT = 0x0B00
 SST_CAT0 = 0x0B10
 SST_WORLD = 0x0B20
+AXOUT = 0x0AA6          # the driver stores the last call's AX here
 
 # A .COM is loaded at CS:0100, and every address in the image is an image
 # offset.  The load bias is therefore the difference between the two, and
@@ -104,7 +106,7 @@ ENTS = ["initmem", "progend", "stackchk", "wrint", "wrchar", "wrbool",
         "wrreal", "wrln", "rdint", "rdchar", "rdbool", "rdln", "halt",
         "wrtinl", "rdbyte", "wrstr", "rdstr", "strstore", "cmpstr", "strcat",
         "wrintw", "real_from_int", "real_add", "real_sub", "real_mul",
-        "real_div"]
+        "real_div", "real_cmp", "real_int"]
 
 # A call slot's flags, as rtdrv.s reads them.
 F_STACK = 1     # the argument goes on the stack, popped by the caller
@@ -527,6 +529,59 @@ def build_cases():
                 dump=6, dumpbytes=q, dumpadr=blk,
                 note="the measured MBF bytes, the reference's rediv")
 
+    # --- recmp: AX := -1/0/+1 for a<b, a=b, a>b ------------------------
+    # The driver stores every call's AX at AXOUT, so these cases can read the
+    # result.  The operand block is the same one the arithmetic cases use.
+    seen = set()
+    for bi, (a, b) in enumerate(BLOCKS):
+        if (a, b) in seen:
+            continue
+        seen.add((a, b))
+        blk = 0x0B30 + 16 * bi
+        want = (a > b) - (a < b)
+        add("real_cmp(%d,%d)" % (a, b),
+            [s("real_cmp", blk + LOAD_BIAS)],
+            dump=2, dumpbytes=struct.pack("<h", want), dumpadr=AXOUT,
+            note="the reference's recmp, returned as AX = -1/0/+1")
+
+    # Fractional blocks, compared through the exponent/mantissa path.
+    FRAC = [(3.7, 3.7), (3.9, 3.7), (-3.7, -3.9), (0.5, 0.25),
+            (-0.5, 0.5), (12345.75, 12345.5), (2.5, 0.0), (-2.5, 0.0),
+            (0.99, 0.0), (1.9, 0.0)]
+    for fi, (a, b) in enumerate(FRAC):
+        blk = 0x0C70 + 16 * fi
+        want = (a > b) - (a < b)
+        add("real_cmp(%g,%g)" % (a, b),
+            [s("real_cmp", blk + LOAD_BIAS)],
+            dump=2, dumpbytes=struct.pack("<h", want), dumpadr=AXOUT,
+            note="fractional magnitudes: the mantissa decides")
+    add("real_cmp(2^40,0)",
+        [s("real_cmp", 0x0D10 + LOAD_BIAS)],
+        dump=2, dumpbytes=struct.pack("<h", 1), dumpadr=AXOUT,
+        note="the exponent alone decides")
+
+    # --- reint: Int(real), in place --------------------------------------
+    # a is the real; the block's b is unused.  The expected bytes are the
+    # measured MBF of trunc(a), because reint returns a REAL, not an integer
+    # (TPSRC's xint pushes the six-byte result back).
+    for bi, (a, b) in enumerate(BLOCKS):
+        if a in (0, 1, -3, 32767):
+            blk = 0x0B30 + 16 * bi
+            add("real_int(%d)" % a,
+                [s("real_int", blk + LOAD_BIAS)],
+                dump=6, dumpbytes=mbf(math.trunc(a)), dumpadr=blk,
+                note="the integer part as a real, the reference's reint")
+    for fi, (a, b) in enumerate(FRAC):
+        blk = 0x0C70 + 16 * fi
+        add("real_int(%g)" % a,
+            [s("real_int", blk + LOAD_BIAS)],
+            dump=6, dumpbytes=mbf(math.trunc(a)), dumpadr=blk,
+            note="the fraction is masked away; the sign and exponent stay")
+    add("real_int(2^40)",
+        [s("real_int", 0x0D10 + LOAD_BIAS)],
+        dump=6, dumpbytes=bytes.fromhex("A90000000000"), dumpadr=0x0D10,
+        note="exponent >= 0A8h is already integral and returns unchanged")
+
     add("wrchar('A')", [s("wrchar", ord("A"))], out=b"A")
     add("wrchar('!')", [s("wrchar", ord("!"))], out=b"!")
 

+ 172 - 55
shell/tests/runtime.golden

@@ -41,7 +41,7 @@
 0036  58          pop ax
 0037  F7 D8       neg ax
 0039  B9 0A 00    mov cx,0ah
-003C  BB 02 08    mov bx,802h
+003C  BB E4 08    mov bx,8e4h
 003F  89 DE       mov si,bx
 0041  31 D2       xor dx,dx
 0043  F7 F1       div ax,cx
@@ -93,7 +93,7 @@
 00A2  58          pop ax
 00A3  F7 D8       neg ax
 00A5  B9 0A 00    mov cx,0ah
-00A8  BB 02 08    mov bx,802h
+00A8  BB E4 08    mov bx,8e4h
 00AB  89 DE       mov si,bx
 00AD  31 D2       xor dx,dx
 00AF  F7 F1       div ax,cx
@@ -125,18 +125,18 @@
 00DE  83 7E 04 00 cmp word ptr [bp+4h],0h
 00E2  89 EC       mov sp,bp
 00E4  75 05       jne 0ebh
-00E6  BA 07 08    mov dx,807h
+00E6  BA E9 08    mov dx,8e9h
 00E9  EB 03       jmp 0eeh
-00EB  BA 02 08    mov dx,802h
+00EB  BA E4 08    mov dx,8e4h
 00EE  B4 09       mov ah,9h
 00F0  CD 21       int 21h
 00F2  5D          pop bp
 00F3  C3          ret
-00F4  BA 10 08    mov dx,810h
+00F4  BA F2 08    mov dx,8f2h
 00F7  B4 09       mov ah,9h
 00F9  CD 21       int 21h
 00FB  C3          ret
-00FC  BA 0D 08    mov dx,80dh
+00FC  BA EF 08    mov dx,8efh
 00FF  B4 09       mov ah,9h
 0101  CD 21       int 21h
 0103  C3          ret
@@ -303,12 +303,12 @@
 0217  EB EF       jmp 208h
 0219  58          pop ax
 021A  C3          ret
-021B  8B 1E 16 08 mov bx,word ptr [816h]
+021B  8B 1E F8 08 mov bx,word ptr [8f8h]
 021F  83 FB 00    cmp bx,0h
 0222  74 09       je 22dh
 0224  8A C3       mov al,bl
 0226  31 DB       xor bx,bx
-0228  89 1E 16 08 mov word ptr [816h],bx
+0228  89 1E F8 08 mov word ptr [8f8h],bx
 022C  C3          ret
 022D  B4 08       mov ah,8h
 022F  CD 21       int 21h
@@ -316,7 +316,7 @@
 0232  88 C2       mov dl,al
 0234  BB 01 00    mov bx,1h
 0237  8A DA       mov bl,dl
-0239  89 1E 16 08 mov word ptr [816h],bx
+0239  89 1E F8 08 mov word ptr [8f8h],bx
 023D  C3          ret
 023E  55          push bp
 023F  8B EC       mov bp,sp
@@ -541,7 +541,7 @@
 03BE  8B 4D 06    mov cx,word ptr [di+6h]
 03C1  8B 75 08    mov si,word ptr [di+8h]
 03C4  8B 7D 0A    mov di,word ptr [di+0ah]
-03C7  C7 06 18 08 00 00 mov word ptr [818h],0h
+03C7  C7 06 FA 08 00 00 mov word ptr [8fah],0h
 03CD  E8 4D 00    call 41dh
 03D0  5F          pop di
 03D1  89 05       mov word ptr [di],ax
@@ -570,7 +570,7 @@
 03FA  8B 4D 06    mov cx,word ptr [di+6h]
 03FD  8B 75 08    mov si,word ptr [di+8h]
 0400  8B 7D 0A    mov di,word ptr [di+0ah]
-0403  C7 06 18 08 00 80 mov word ptr [818h],8000h
+0403  C7 06 FA 08 00 80 mov word ptr [8fah],8000h
 0409  E8 11 00    call 41dh
 040C  5F          pop di
 040D  89 05       mov word ptr [di],ax
@@ -585,7 +585,7 @@
 041C  C3          ret
 041D  80 F9 00    cmp cl,0h
 0420  74 0E       je 430h
-0422  33 3E 18 08 xor di,word ptr [818h]
+0422  33 3E FA 08 xor di,word ptr [8fah]
 0426  3C 00       cmp al,0h
 0428  75 07       jne 431h
 042A  89 C8       mov ax,cx
@@ -597,16 +597,16 @@
 0435  91          xchg ax,cx
 0436  87 F3       xchg bx,si
 0438  87 FA       xchg dx,di
-043A  88 0E 1C 08 mov byte ptr [81ch],cl
+043A  88 0E FE 08 mov byte ptr [8feh],cl
 043E  28 C1       sub cl,al
 0440  80 F9 28    cmp cl,28h
 0443  72 06       jb 44bh
-0445  8A 0E 1C 08 mov cl,byte ptr [81ch]
+0445  8A 0E FE 08 mov cl,byte ptr [8feh]
 0449  EB DF       jmp 42ah
-044B  89 3E 18 08 mov word ptr [818h],di
-044F  80 26 19 08 80 and byte ptr [819h],80h
-0454  89 3E 1A 08 mov word ptr [81ah],di
-0458  30 36 1B 08 xor byte ptr [81bh],dh
+044B  89 3E FA 08 mov word ptr [8fah],di
+044F  80 26 FB 08 80 and byte ptr [8fbh],80h
+0454  89 3E FC 08 mov word ptr [8fch],di
+0458  30 36 FD 08 xor byte ptr [8fdh],dh
 045C  81 CF 00 80 or di,8000h
 0460  80 CE 80    or dh,80h
 0463  80 F9 10    cmp cl,10h
@@ -631,8 +631,8 @@
 048E  D0 DC       rcr ah,1h
 0490  FE C9       dec cl
 0492  75 F6       jne 48ah
-0494  A0 1C 08    mov al,byte ptr [81ch]
-0497  F6 06 1B 08 80 test byte ptr [81bh],80h
+0494  A0 FE 08    mov al,byte ptr [8feh]
+0497  F6 06 FD 08 80 test byte ptr [8fdh],80h
 049C  75 14       jne 4b2h
 049E  00 EC       add ah,ch
 04A0  11 F3       adc bx,si
@@ -652,7 +652,7 @@
 04BA  19 F3       sbb bx,si
 04BC  19 FA       sbb dx,di
 04BE  73 14       jnb 4d4h
-04C0  80 36 19 08 80 xor byte ptr [819h],80h
+04C0  80 36 FB 08 80 xor byte ptr [8fbh],80h
 04C5  F6 D4       not ah
 04C7  F7 D3       not bx
 04C9  F7 D2       not dx
@@ -684,7 +684,7 @@
 0502  31 D2       xor dx,dx
 0504  C3          ret
 0505  80 E6 7F    and dh,7fh
-0508  32 36 19 08 xor dh,byte ptr [819h]
+0508  32 36 FB 08 xor dh,byte ptr [8fbh]
 050C  C3          ret
 050D  55          push bp
 050E  8B EC       mov bp,sp
@@ -730,23 +730,23 @@
 0561  B0 01       mov al,1h
 0563  B4 4C       mov ah,4ch
 0565  CD 21       int 21h
-0567  89 0E 23 08 mov word ptr [823h],cx
+0567  89 0E 05 09 mov word ptr [905h],cx
 056B  8B CA       mov cx,dx
 056D  31 F9       xor cx,di
 056F  F6 D5       not ch
 0571  80 E5 80    and ch,80h
-0574  88 2E 19 08 mov byte ptr [819h],ch
+0574  88 2E FB 08 mov byte ptr [8fbh],ch
 0578  80 CE 80    or dh,80h
 057B  81 CF 00 80 or di,8000h
-057F  89 36 25 08 mov word ptr [825h],si
-0583  89 3E 27 08 mov word ptr [827h],di
-0587  A3 1D 08    mov word ptr [81dh],ax
-058A  89 1E 1F 08 mov word ptr [81fh],bx
-058E  89 16 21 08 mov word ptr [821h],dx
+057F  89 36 07 09 mov word ptr [907h],si
+0583  89 3E 09 09 mov word ptr [909h],di
+0587  A3 FF 08    mov word ptr [8ffh],ax
+058A  89 1E 01 09 mov word ptr [901h],bx
+058E  89 16 03 09 mov word ptr [903h],dx
 0592  30 E4       xor ah,ah
 0594  31 DB       xor bx,bx
 0596  31 D2       xor dx,dx
-0598  BF 23 08    mov di,823h
+0598  BF 05 09    mov di,905h
 059B  B1 05       mov cl,5h
 059D  47          inc di
 059E  8A 2D       mov ch,byte ptr [di]
@@ -761,9 +761,9 @@
 05B1  BE 08 00    mov si,8h
 05B4  D0 DD       rcr ch,1h
 05B6  73 0C       jnb 5c4h
-05B8  02 26 1E 08 add ah,byte ptr [81eh]
-05BC  13 1E 1F 08 adc bx,word ptr [81fh]
-05C0  13 16 21 08 adc dx,word ptr [821h]
+05B8  02 26 00 09 add ah,byte ptr [900h]
+05BC  13 1E 01 09 adc bx,word ptr [901h]
+05C0  13 16 03 09 adc dx,word ptr [903h]
 05C4  D1 DA       rcr dx,1h
 05C6  D1 DB       rcr bx,1h
 05C8  D0 DC       rcr ah,1h
@@ -783,7 +783,7 @@
 05E2  74 02       je 5e6h
 05E4  FE C9       dec cl
 05E6  91          xchg ax,cx
-05E7  32 36 19 08 xor dh,byte ptr [819h]
+05E7  32 36 FB 08 xor dh,byte ptr [8fbh]
 05EB  3C 00       cmp al,0h
 05ED  75 06       jne 5f5h
 05EF  31 C0       xor ax,ax
@@ -832,29 +832,29 @@
 0643  B0 01       mov al,1h
 0645  B4 4C       mov ah,4ch
 0647  CD 21       int 21h
-0649  89 0E 23 08 mov word ptr [823h],cx
+0649  89 0E 05 09 mov word ptr [905h],cx
 064D  8B CA       mov cx,dx
 064F  31 F9       xor cx,di
 0651  F6 D5       not ch
 0653  80 E5 80    and ch,80h
-0656  88 2E 19 08 mov byte ptr [819h],ch
+0656  88 2E FB 08 mov byte ptr [8fbh],ch
 065A  80 CE 80    or dh,80h
 065D  81 CF 00 80 or di,8000h
-0661  89 36 25 08 mov word ptr [825h],si
-0665  89 3E 27 08 mov word ptr [827h],di
-0669  A2 1D 08    mov byte ptr [81dh],al
-066C  BF 22 08    mov di,822h
+0661  89 36 07 09 mov word ptr [907h],si
+0665  89 3E 09 09 mov word ptr [909h],di
+0669  A2 FF 08    mov byte ptr [8ffh],al
+066C  BF 04 09    mov di,904h
 066F  B1 05       mov cl,5h
 0671  BE 08 00    mov si,8h
-0674  3B 16 27 08 cmp dx,word ptr [827h]
+0674  3B 16 09 09 cmp dx,word ptr [909h]
 0678  75 0A       jne 684h
-067A  3B 1E 25 08 cmp bx,word ptr [825h]
+067A  3B 1E 07 09 cmp bx,word ptr [907h]
 067E  75 04       jne 684h
-0680  3A 26 24 08 cmp ah,byte ptr [824h]
+0680  3A 26 06 09 cmp ah,byte ptr [906h]
 0684  72 0C       jb 692h
-0686  2A 26 24 08 sub ah,byte ptr [824h]
-068A  1B 1E 25 08 sbb bx,word ptr [825h]
-068E  1B 16 27 08 sbb dx,word ptr [827h]
+0686  2A 26 06 09 sub ah,byte ptr [906h]
+068A  1B 1E 07 09 sbb bx,word ptr [907h]
+068E  1B 16 09 09 sbb dx,word ptr [909h]
 0692  F5          cmc
 0693  D0 D5       rcl ch,1h
 0695  4E          dec si
@@ -868,24 +868,24 @@
 06A4  D1 D3       rcl bx,1h
 06A6  D1 D2       rcl dx,1h
 06A8  73 CA       jnb 674h
-06AA  2A 26 24 08 sub ah,byte ptr [824h]
-06AE  1B 1E 25 08 sbb bx,word ptr [825h]
-06B2  1B 16 27 08 sbb dx,word ptr [827h]
+06AA  2A 26 06 09 sub ah,byte ptr [906h]
+06AE  1B 1E 07 09 sbb bx,word ptr [907h]
+06B2  1B 16 09 09 sbb dx,word ptr [909h]
 06B6  F8          clc
 06B7  EB D9       jmp 692h
 06B9  D0 E4       shl ah,1h
 06BB  D1 D3       rcl bx,1h
 06BD  D1 D2       rcl dx,1h
 06BF  72 11       jb 6d2h
-06C1  3B 16 27 08 cmp dx,word ptr [827h]
+06C1  3B 16 09 09 cmp dx,word ptr [909h]
 06C5  75 0A       jne 6d1h
-06C7  3B 1E 25 08 cmp bx,word ptr [825h]
+06C7  3B 1E 07 09 cmp bx,word ptr [907h]
 06CB  75 04       jne 6d1h
-06CD  3A 26 24 08 cmp ah,byte ptr [824h]
+06CD  3A 26 06 09 cmp ah,byte ptr [906h]
 06D1  F5          cmc
-06D2  8B 0E 1D 08 mov cx,word ptr [81dh]
-06D6  8B 1E 1F 08 mov bx,word ptr [81fh]
-06DA  8B 16 21 08 mov dx,word ptr [821h]
+06D2  8B 0E FF 08 mov cx,word ptr [8ffh]
+06D6  8B 1E 01 09 mov bx,word ptr [901h]
+06DA  8B 16 03 09 mov dx,word ptr [903h]
 06DE  9F          lahf
 06DF  F6 C6 80    test dh,80h
 06E2  75 09       jne 6edh
@@ -900,3 +900,120 @@
 06F3  B4 4C       mov ah,4ch
 06F5  CD 21       int 21h
 06F7  E9 EC FE    jmp 5e6h
+06FA  55          push bp
+06FB  8B EC       mov bp,sp
+06FD  53          push bx
+06FE  52          push dx
+06FF  56          push si
+0700  57          push di
+0701  8B 76 04    mov si,word ptr [bp+4h]
+0704  8B 04       mov ax,word ptr [si]
+0706  8B 4C 02    mov cx,word ptr [si+2h]
+0709  8B 54 04    mov dx,word ptr [si+4h]
+070C  89 CB       mov bx,cx
+070E  8B FE       mov di,si
+0710  8B 4D 06    mov cx,word ptr [di+6h]
+0713  8B 75 08    mov si,word ptr [di+8h]
+0716  8B 7D 0A    mov di,word ptr [di+0ah]
+0719  52          push dx
+071A  31 FA       xor dx,di
+071C  5A          pop dx
+071D  79 06       jns 725h
+071F  52          push dx
+0720  D1 D2       rcl dx,1h
+0722  5A          pop dx
+0723  EB 10       jmp 735h
+0725  F6 C6 80    test dh,80h
+0728  74 08       je 732h
+072A  E8 20 00    call 74dh
+072D  74 06       je 735h
+072F  F5          cmc
+0730  EB 03       jmp 735h
+0732  E8 18 00    call 74dh
+0735  B0 00       mov al,0h
+0737  B4 00       mov ah,0h
+0739  72 06       jb 741h
+073B  74 08       je 745h
+073D  B0 01       mov al,1h
+073F  EB 04       jmp 745h
+0741  B0 FF       mov al,0ffh
+0743  B4 FF       mov ah,0ffh
+0745  5F          pop di
+0746  5E          pop si
+0747  5A          pop dx
+0748  5B          pop bx
+0749  89 EC       mov sp,bp
+074B  5D          pop bp
+074C  C3          ret
+074D  38 C8       cmp al,cl
+074F  75 0E       jne 75fh
+0751  3C 00       cmp al,0h
+0753  74 0A       je 75fh
+0755  39 FA       cmp dx,di
+0757  75 06       jne 75fh
+0759  39 F3       cmp bx,si
+075B  75 02       jne 75fh
+075D  38 EC       cmp ah,ch
+075F  C3          ret
+0760  55          push bp
+0761  8B EC       mov bp,sp
+0763  53          push bx
+0764  52          push dx
+0765  56          push si
+0766  57          push di
+0767  FF 76 04    push word ptr [bp+4h]
+076A  8B 76 04    mov si,word ptr [bp+4h]
+076D  8B 04       mov ax,word ptr [si]
+076F  8B 4C 02    mov cx,word ptr [si+2h]
+0772  8B 54 04    mov dx,word ptr [si+4h]
+0775  89 CB       mov bx,cx
+0777  3C A8       cmp al,0a8h
+0779  73 4C       jnb 7c7h
+077B  89 C1       mov cx,ax
+077D  89 DE       mov si,bx
+077F  89 D7       mov di,dx
+0781  30 E4       xor ah,ah
+0783  31 DB       xor bx,bx
+0785  31 D2       xor dx,dx
+0787  80 E9 80    sub cl,80h
+078A  76 4C       jbe 7d8h
+078C  80 F9 10    cmp cl,10h
+078F  72 0C       jb 79dh
+0791  88 FC       mov ah,bh
+0793  89 D3       mov bx,dx
+0795  BA FF FF    mov dx,0ffffh
+0798  80 E9 10    sub cl,10h
+079B  EB EF       jmp 78ch
+079D  80 F9 08    cmp cl,8h
+07A0  72 0F       jb 7b1h
+07A2  88 DC       mov ah,bl
+07A4  88 FB       mov bl,bh
+07A6  88 D7       mov bh,dl
+07A8  88 F2       mov dl,dh
+07AA  B6 FF       mov dh,0ffh
+07AC  80 E9 08    sub cl,8h
+07AF  EB EC       jmp 79dh
+07B1  80 F9 00    cmp cl,0h
+07B4  74 0B       je 7c1h
+07B6  F9          stc
+07B7  D1 DA       rcr dx,1h
+07B9  D1 DB       rcr bx,1h
+07BB  D0 DC       rcr ah,1h
+07BD  FE C9       dec cl
+07BF  75 F5       jne 7b6h
+07C1  21 FA       and dx,di
+07C3  21 F3       and bx,si
+07C5  20 EC       and ah,ch
+07C7  5F          pop di
+07C8  89 05       mov word ptr [di],ax
+07CA  89 5D 02    mov word ptr [di+2h],bx
+07CD  89 55 04    mov word ptr [di+4h],dx
+07D0  5F          pop di
+07D1  5E          pop si
+07D2  5A          pop dx
+07D3  5B          pop bx
+07D4  89 EC       mov sp,bp
+07D6  5D          pop bp
+07D7  C3          ret
+07D8  B0 00       mov al,0h
+07DA  EB EB       jmp 7c7h