Prechádzať zdrojové kódy

runtime: the first MBF routine, integer -> 6-byte Real

The start of the Real stage: a runtime entry, not compiler syntax yet.

real_from_int(src, dst) transcribes TPSRC3's intreal instruction for
instruction.  ABI: the source word's address at [BP+6], the
destination's at [BP+4].  The register convention is the reference's:
AL = exponent, AH+BX = the low 39 mantissa bits, DX = the high 16 with
the sign in DH bit 7.  Zero, sign, the 16-bit fast path and the
normalisation loop are all kept; the packed bytes are then the two
stores.  Checked against CALC-PORT.md's measured table for 0, 1, 2, 3,
5, 7, 42, 1000, 32767 and -1/-2/-42/-32768 in rt_exec (69/69 cases).

The tools had to grow with it:
  * rt_exec gains the entry and a two-word call helper; the cases put the
    integer in the record's input bytes and dump the six result bytes.
  * RtProbe reports the new entry; check_runtime blesses the runtime
    golden and gains a semantic golden for the entry's frame.
  * FCML rejects `09 /r` register-register (OR DX,DX), so the flag tests
    use CMP reg,0 - the same ZF/SF/CF for a non-negative register.  The
    audit gains patterns for CMP reg,0, SHL/SHR by one, and 8-bit XCHG,
    plus documented exclusions for the parameterised helpers.
  * check_8086's clause F assumed a clean linear sweep meant "no inline
    strings".  "don't" decodes as instructions, and the runtime growing
    changed the following CALL displacement so its 0F surfaced.  The
    sweep now recognizes CALL wrtinl from the probe's entry offset and
    steps over <len><chars>; t24 sweeps clean again for the right reason.

Non-vacuity mutates intreal's exponent base (90h -> 8Fh) and requires
rt_exec's six-byte comparisons to go red.

The ladder is unchanged at 18267: readd/resub/remult/rediv, recmp,
reint and the formatter are the next MBF routines, and the compiler
syntax for real expressions comes after them.
Eric Streit 2 dní pred
rodič
commit
05f118f360

+ 3 - 0
shell/Runtime.def

@@ -50,6 +50,9 @@ CONST
                                value is at [BP+6] and the field width at
                                [BP+4]; pads with spaces on the left when the
                                number is shorter than the width *)
+   E_RealFromInt = 21 ;     (* integer -> 6-byte MBF real: the source word's
+                               address is at [BP+6] and the destination's at
+                               [BP+4] *)
 
    (* 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

+ 96 - 2
shell/Runtime.mod

@@ -127,7 +127,7 @@ VAR
    dataAt : CARDINAL ;
    rtBase : CARDINAL ;
    built  : BOOLEAN ;
-   entNm  : ARRAY [0..20] OF ARRAY [0..MaxNm] OF CHAR ;
+   entNm  : ARRAY [0..21] OF ARRAY [0..MaxNm] OF CHAR ;
 
 (* ---------------------------------------------------------------- *)
 (*  name helpers                                                     *)
@@ -553,6 +553,30 @@ PROCEDURE MovAlBl  ; BEGIN B (8AH) ; B (0C3H) END MovAlBl ; (* 8A C3: AL:=BL *)
 PROCEDURE MovClBx  ; BEGIN B (8AH) ; B (0FH) END MovClBx ;  (* 8A 0F: CL:=[BX] *)
 PROCEDURE XorBxBx  ; BEGIN B (31H) ; B (0DBH) END XorBxBx ;  (* 31 DB: BX:=0 *)
 PROCEDURE MovBlDl  ; BEGIN B (8AH) ; B (0DAH) END MovBlDl ;  (* 8A DA: BL:=DL *)
+PROCEDURE MovBhAh  ; BEGIN B (88H) ; B (0E7H) END MovBhAh ;  (* 88 E7: BH:=AH *)
+PROCEDURE MovDxAx  ; BEGIN B (89H) ; B (0C2H) END MovDxAx ;  (* 89 C2: DX:=AX *)
+PROCEDURE MovSiBp4 ; BEGIN B (8BH) ; B (76H) ; B (4) END MovSiBp4 ; (* SI:=[BP+4] *)
+PROCEDURE MovSiBp6 ; BEGIN B (8BH) ; B (76H) ; B (6) END MovSiBp6 ; (* SI:=[BP+6] *)
+PROCEDURE LdAxSi   ; BEGIN B (8BH) ; B (04H) END LdAxSi ;    (* 8B 04: AX:=[SI] *)
+PROCEDURE StDiOffBx (off : CARDINAL) ;
+BEGIN B (89H) ; B (5DH) ; B (off MOD 256) END StDiOffBx ; (* MOV [DI+o],BX *)
+PROCEDURE StDiOffDx (off : CARDINAL) ;
+BEGIN B (89H) ; B (55H) ; B (off MOD 256) END StDiOffDx ; (* MOV [DI+o],DX *)
+PROCEDURE Js8  (nm : ARRAY OF CHAR ) ; BEGIN Jcc (78H, nm) END Js8 ;
+PROCEDURE Jns8 (nm : ARRAY OF CHAR ) ; BEGIN Jcc (79H, nm) END Jns8 ;
+PROCEDURE Jmp8 (nm : ARRAY OF CHAR ) ;
+BEGIN B (0EBH) ; AddFix (0, rpos, nm, 0) ; B (0) END Jmp8 ;
+PROCEDURE MovAxImm (v : CARDINAL ) ; BEGIN B (0B8H) ; W (v) END MovAxImm ;
+PROCEDURE XchgDlDh ; BEGIN B (86H) ; B (0D6H) END XchgDlDh ; (* 86 D6: DL<->DH *)
+PROCEDURE DecAl    ; BEGIN B (0FEH) ; B (0C8H) END DecAl ;    (* FE C8 *)
+PROCEDURE ShlDx1   ; BEGIN B (0D1H) ; B (0E2H) END ShlDx1 ;   (* D1 E2: SHL DX,1 *)
+PROCEDURE NegDx    ; BEGIN B (0F7H) ; B (0DAH) END NegDx ;    (* F7 DA: NEG DX *)
+PROCEDURE CmpDh0   ; BEGIN B (80H) ; B (0FEH) ; B (0) END CmpDh0 ; (* 80 FE 00 *)
+PROCEDURE CmpBh0   ; BEGIN B (80H) ; B (0FFH) ; B (0) END CmpBh0 ; (* 80 FF 00 *)
+PROCEDURE CmpDx0   ; BEGIN B (83H) ; B (0FAH) ; B (0) END CmpDx0 ; (* 83 FA 00 *)
+PROCEDURE CmpAl0   ; BEGIN B (3CH) ; B (0) END CmpAl0 ;         (* 3C 00 *)
+PROCEDURE AndDhImm (v : CARDINAL) ;
+BEGIN B (80H) ; B (0E6H) ; B (v MOD 256) END AndDhImm ;   (* 80 E6 imm *)
 PROCEDURE StVxBx   (delta : CARDINAL ) ;
 (* 89 1E lo hi: MOV [Vx],BX - the mirror of LdBxVx above, and it must use the
    same mod=00 / rm=110 and the same Dd fixup kind, or one of the two
@@ -1390,6 +1414,74 @@ BEGIN
    END
 END Str5 ;
 
+PROCEDURE EmitRealFromInt ;
+(* intreal (TPSRC3 130): an INTEGER -> the 6-byte MBF real.
+
+   ABI: the source word's ADDRESS is at [BP+6] and the destination's at
+   [BP+4]; the caller pushes the source first, so [BP+6] is the source.  (The
+   reference keeps the real in AX/BX/DX, where AL is the exponent byte, AH
+   and BX the low 39 mantissa bits and DX the high 16 with the sign in DH's
+   bit 7.  Packing is therefore just the two stores.)
+
+   The arithmetic is the reference's, instruction for instruction:
+
+     zero -> all six bytes zero
+     sign -> BH, DX := |value|
+     AX := 0090h (exponent 144, clear mantissa)
+     small value: exponent 88h and shift the mantissa 8
+     normalise: shift DX left and decrement AL until DX's sign bit is set
+     positive: clear DH bit 7; negative: leave it as the sign
+     clear the rest of the mantissa (BX)
+
+   The sign convention here is the packed one: bit 7 of the last byte is the
+   sign, and the implicit 1 is not stored.  That is why intreal leaves DX's
+   top bit alone for negatives (it becomes the sign) and clears it for
+   positives. *)
+BEGIN
+   M ("real_from_int") ;
+   PushBp ; MovBpSp ;
+   PushBx ; PushDx ; PushSi ; PushDi ;
+   MovSiBp6 ;                       (* SI := source word *)
+   LdAxSi ;                         (* AX := the integer *)
+   CmpAx0 ;
+   Jne8 ("rf_nz") ;
+   XorBxBx ;
+   XorDxDx ;
+   Jmp8 ("rf_store") ;
+   M ("rf_nz") ;
+   MovBhAh ;                        (* BH := the integer's sign byte *)
+   MovDxAx ;
+   CmpDx0 ;
+   Jns8 ("rf_pos") ;
+   NegDx ;
+   M ("rf_pos") ;
+   MovAxImm (0090H) ;               (* AL = exponent 144, AH = 0 *)
+   CmpDh0 ;
+   Jne8 ("rf_16") ;
+   MovAlD (088H) ;                  (* 16-bit value in the top byte: bias 88 *)
+   XchgDlDh ;
+   M ("rf_16") ;
+   CmpDx0 ;
+   Js8 ("rf_done") ;
+   M ("rf_nrm") ;
+   DecAl ;
+   ShlDx1 ;
+   Jns8 ("rf_nrm") ;
+   M ("rf_done") ;
+   CmpBh0 ;
+   Js8 ("rf_pos2") ;
+   AndDhImm (7FH) ;                 (* positive: bit 7 of DH is not a sign *)
+   M ("rf_pos2") ;
+   XorBxBx ;                        (* only DX's 16 bits carry the value now *)
+   M ("rf_store") ;
+   MovDiBp4 ;                       (* DI := the destination *)
+   StDiAx ;                         (* bytes 0..1 *)
+   StDiOffBx (2) ;                  (* bytes 2..3 *)
+   StDiOffDx (4) ;                  (* bytes 4..5, sign in DH bit 7 *)
+   PopDi ; PopSi ; PopDx ; PopBx ;
+   MovSpBp ; PopBp ; RetR
+END EmitRealFromInt ;
+
 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
@@ -1486,6 +1578,7 @@ BEGIN
    EmitRdChar ; EmitRdBool ; EmitRdLn ;
    EmitGetCh ; EmitUnGetCh ;
    EmitRdStr ; EmitStrStore ; EmitCmpStr ; EmitStrCat ;
+   EmitRealFromInt ;
    EmitData ;
    FixUp ;
    SetStr (entNm [0], "initmem") ;
@@ -1509,6 +1602,7 @@ BEGIN
    SetStr (entNm [18], "cmpstr") ;
    SetStr (entNm [19], "strcat") ;
    SetStr (entNm [20], "wrintw") ;
+   SetStr (entNm [21], "real_from_int") ;
    built := TRUE
 END RT_Build ;
 
@@ -1536,7 +1630,7 @@ BEGIN
    IF NOT built THEN
       RT_Build (0)
    END ;
-   IF i > 20 THEN
+   IF i > 21 THEN
       RETURN 0
    END ;
    RETURN rtBase + LblOff (entNm [i])

+ 4 - 3
shell/tests/RtProbe.mod

@@ -108,7 +108,7 @@ END PENT ;
 
 PROCEDURE Main ;
 VAR i, n, base : CARDINAL ;
-    names : ARRAY [0..20] OF ARRAY [0..15] OF CHAR ;
+    names : ARRAY [0..21] 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 <= 20 DO
+   WHILE i <= 21 DO
       names [i] := "" ;
       INC (i)
    END ;
@@ -133,8 +133,9 @@ BEGIN
    names [15] := "wrstr" ;  names [16] := "rdstr" ;  names [17] := "strstore" ;
    names [18] := "cmpstr" ; names [19] := "strcat" ;
    names [20] := "wrintw" ;
+   names [21] := "real_from_int" ;
    i := 0 ;
-   WHILE i <= 20 DO
+   WHILE i <= 21 DO
       PENT (i, names [i]) ;
       INC (i)
    END ;

+ 14 - 0
shell/tests/audit_helpers.py

@@ -102,6 +102,10 @@ DATA = "emits DATA (the D_ block), not instructions"
 
 NON_ONE_LINE = {
     "Runtime.mod": {
+        # Real (MBF) entry helpers whose operands are data/field offsets or
+        # immediates; each is a fixed sequence, pinned by rt_exec's MBF cases.
+        "MovAxImm": COMP, "Jmp8": COMP, "AndDhImm": COMP,
+        "StDiOffBx": COMP, "StDiOffDx": COMP,
         "MovBxImm": PARAM, "MovCxImm": PARAM, "MovDxImm": PARAM,
         "MovDl": PARAM, "MovAlD": PARAM, "MovAh": PARAM,
         "SubAl": PARAM, "CmpAl": PARAM, "AddDl": PARAM, "J8": COMP,
@@ -353,6 +357,10 @@ PATTERNS = [
     (re.compile(r"^Xchg(%s)(%s)$" % (_ALT, _ALT)),
      "xchg", ["rx:%(1)s|%(2)s"], {0x87, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
                                   0x97}),
+    # 8-bit XCHG, 86 /r.  The MBF intreal swaps DL and DH to move the low byte
+    # of a 16-bit value into the exponent position.
+    (re.compile(r"^Xchg(Al|Cl|Dl|Bl|Ah|Ch|Dh|Bh)(Al|Cl|Dl|Bl|Ah|Ch|Dh|Bh)$"),
+     "xchg", ["rx:%(1)s|%(2)s"], {0x86}),
     # --- the accumulator-implicit XCHGs, 91h..97h, join the /r form -----
     # One byte, no ModRM, both registers fixed by the opcode.  They are in
     # the same opcode set because the same `rx:` set comparison decides them,
@@ -372,7 +380,13 @@ PATTERNS = [
     # --- shifts: by one, and by CL.  The set membership test uses both:
     # D1 E8 (SHR AX,1) three times, and D2 E2 (SHL DL,CL) for the mask.
     (re.compile(r"^ShrAx(1)$"), "shr", ["r:Ax", "i:%(1)s"], {0xD1}),
+    (re.compile(r"^ShlDx(1)$"), "shl", ["r:Dx", "i:%(1)s"], {0xD1}),
     (re.compile(r"^ShlDlCl$"),  "shl", ["r:Dl", "r:Cl"],     {0xD2}),
+    # --- CMP reg,0 as a flag test (the 8086 has no OR reg,reg FCML can
+    #     decode here; CMP sets the same ZF/SF/CF for a non-negative reg) ---
+    (re.compile(r"^CmpAl0$"), "cmp", ["r:Al", "i:0"], {0x3C}),
+    (re.compile(r"^Cmp(Cl|Dl|Bl|Ah|Ch|Dh|Bh)0$"),
+     "cmp", ["r:%(1)s", "i:0"], {0x80}),
     (re.compile(r"^MovAh(0|1)$"),
      "mov", ["r:Ah", "i:%(1)s"], {0xB4}),
     (re.compile(r"^MovAl(0|1)$"),

+ 32 - 6
shell/tests/check_8086.py

@@ -146,11 +146,15 @@ def probe_runtime():
         sys.stderr.write(out.stdout + out.stderr)
         raise SystemExit("FAIL: rt_exec.py --probe failed")
     size = code_end = None
+    entries = {}
     for line in out.stdout.splitlines():
         if line.endswith("bytes") and size is None:
             size = int(line.split()[0])
         if line.startswith("code ends at"):
             code_end = int(line.split()[3])
+        m = re.match(r"^  entry \d+ = (\d+)\s+\((\w+)\)$", line)
+        if m:
+            entries[m.group(2)] = int(m.group(1))
     if size is None or code_end is None:
         raise SystemExit("FAIL: could not read the runtime size from the probe")
     # the probe prints a hex dump of the blob; rebuild it from the .COM-free
@@ -164,7 +168,9 @@ def probe_runtime():
                 blob += bytes(int(p, 16) for p in parts[1:])
             except ValueError:
                 pass
-    return bytes(blob), code_end, size
+    if "wrtinl" not in entries:
+        raise SystemExit("FAIL: the runtime probe did not report wrtinl")
+    return bytes(blob), code_end, size, entries
 
 
 # Fixtures whose CODE REGION deliberately holds DATA.  A set or array typed
@@ -178,12 +184,29 @@ def probe_runtime():
 CODE_RESIDENT_CONST = {"t61_setconst", "t64_arrays"}
 
 
-def sweep(code, base=0):
+def sweep(code, base=0, wrtinl=None):
     """Linear sweep.  Returns (instructions, offset_it_stopped_at_or_None).
-    An instruction is (offset, opcode_byte, length)."""
+    An instruction is (offset, opcode_byte, length).
+
+    `wrtinl' is the IMAGE address of the wrtinl entry.  When given, a CALL to
+    it is followed by an inline string literal -- `<length><chars>' in the
+    instruction stream, exactly what EmWrInl/wrtinl expect -- and the sweep
+    steps over those bytes instead of decoding the characters as code.  A
+    literal such as "don't" can decode cleanly, so without this the sweep can
+    walk through it into the CALL displacement that follows and report an 0F
+    that is a data byte.
+    """
     out = []
     pc = 0
     while pc < len(code):
+        if (wrtinl is not None and code[pc] == 0xE8 and pc + 3 <= len(code)):
+            rel = code[pc + 1] | (code[pc + 2] << 8)
+            if ((base + pc + 3 + rel) & 0xFFFF) == wrtinl:
+                out.append((pc, 0xE8, 3))
+                pc += 3
+                if pc < len(code):
+                    pc += 1 + code[pc]      # length byte + characters
+                continue
         text, length = disasm16.decode(code[pc:], base + pc)
         if length == 0:
             return out, pc
@@ -281,7 +304,7 @@ def source_operators(path):
 
 def check_runtime_region(verbose):
     """A: the runtime's code region is pure code, so this is exhaustive."""
-    blob, code_end, size = probe_runtime()
+    blob, code_end, size, entries = probe_runtime()
     if code_end > len(blob):
         return ["the probe says code ends at %d but only %d bytes were dumped"
                 % (code_end, len(blob))]
@@ -300,7 +323,8 @@ def check_runtime_region(verbose):
               % (code_end, len(instrs),
                  "" if stopped is None else ", stopped at %04X" % stopped))
     return problems, dict(size=size, code_end=code_end,
-                          ninstr=len(instrs), n0f=len(bad))
+                          ninstr=len(instrs), n0f=len(bad),
+                          entries=entries)
 
 
 def program_code_region(img, rt_size):
@@ -455,7 +479,9 @@ def main(argv):
             # whole thing is code -- assert no 0F opcode over it as well.  This
             # is extra coverage, not the backbone, and how much of the suite
             # it reached is printed rather than implied.
-            instrs, stopped = sweep(code, comimage.LOAD_BIAS + start)
+            wrtinl = comimage.ENT_SZ + rt_info["entries"]["wrtinl"]
+            instrs, stopped = sweep(code, comimage.LOAD_BIAS + start,
+                                    comimage.LOAD_BIAS + wrtinl)
             if stopped is None and name not in CODE_RESIDENT_CONST:
                 swept_fixtures += 1
                 swept_bytes += len(code)

+ 4 - 0
shell/tests/check_runtime.py

@@ -128,6 +128,10 @@ GOLDEN = {
     # becomes the character count.  Wrong offsets here would print a width
     # as a number and a value as a width, both plausible and both silent.
     "wrintw": "55 8B EC BB 00 00 8B 46 06",  # PUSH BP; MOV BP,SP; BX:=0; AX:=[BP+6]
+    # real_from_int: PUSH BP; MOV BP,SP; PUSH BX; PUSH DX; PUSH SI; PUSH DI.
+    # It keeps the destination in DI and reads the source through SI, so both
+    # are saved and restored.
+    "real_from_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

+ 21 - 0
shell/tests/nonvacuity.sh

@@ -1349,6 +1349,27 @@ else
 fi
 cp "$SAVED_C" Compiler.mod
 
+# --- the MBF integer conversion's exponent base ---------------------------
+# intreal starts with exponent 90h for a value whose top byte is set; one less
+# and every real_from_int result carries the wrong exponent.  rt_exec compares
+# the six emitted bytes against CALC-PORT.md's measured table, so this is a
+# behavioural case, not a byte-golden one.
+cp "$SAVED" Runtime.mod
+if mutate Runtime.mod 's|   MovAxImm (0090H)|   MovAxImm (008FH)|'; then
+    if "$GM2" -fiso -c Runtime.mod >/dev/null 2>&1; then
+        expect_red "real_from_int red when the exponent base is 8Fh" \
+            "real_from_int" python3 tests/rt_exec.py
+    else
+        echo "  FAIL: the runtime would not rebuild with the wrong base"
+        fail=$((fail + 1))
+    fi
+else
+    echo "  BROKEN CASE: the MBF exponent-base mutation did not apply"
+    fail=$((fail + 1))
+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

+ 33 - 1
shell/tests/rt_exec.py

@@ -102,7 +102,8 @@ SOH, STX, ETX, EOT = 0x01, 0x02, 0x03, 0x04
 # the lookup fail loudly instead of silently testing offset 0.
 ENTS = ["initmem", "progend", "stackchk", "wrint", "wrchar", "wrbool",
         "wrreal", "wrln", "rdint", "rdchar", "rdbool", "rdln", "halt",
-        "wrtinl", "rdbyte", "wrstr", "rdstr", "strstore", "cmpstr", "strcat"]
+        "wrtinl", "rdbyte", "wrstr", "rdstr", "strstore", "cmpstr", "strcat",
+        "wrintw", "real_from_int"]
 
 # A call slot's flags, as rtdrv.s reads them.
 F_STACK = 1     # the argument goes on the stack, popped by the caller
@@ -436,6 +437,11 @@ def build_cases():
     def i(ent):
         return (ent, 0, F_INL, 0, 0)
 
+    def s2(ent, arg, arg2):
+        """Two stack words: arg2 is pushed first (it lands at [BP+6]), arg
+        second (at [BP+4]).  real_from_int(src, dst) takes its source first."""
+        return (ent, arg, F_STACK | F_STACK2, arg2, 0)
+
     def s2cx(ent, arg, arg2, cx):
         """Two stack words AND CX: arg2 is pushed first (it lands at [BP+6]),
         arg second (at [BP+4]), and CX carries the maximum length.  strstore is
@@ -461,6 +467,32 @@ def build_cases():
         add("wrint(%d)" % v, [s("wrint", v & 0xFFFF)],
             out=str(v).encode(), note=why)
 
+    # --- the 6-byte MBF real: integer -> real ---------------------------
+    # The source word is passed in the case record's input bytes (CASE+5),
+    # and the destination at STORE; both are memory addresses, as always.
+    def mbf(v):
+        """TP3's measured 6-byte Real for an integer (CALC-PORT.md)."""
+        if v == 0:
+            return bytes(6)
+        neg = v < 0
+        a = abs(v)
+        k = a.bit_length() - 1
+        e = 129 + k
+        n = (a - (1 << k)) << (39 - k)
+        b = bytearray([e]) + bytearray((n >> (8 * i)) & 0xFF
+                                       for i in range(5))
+        if neg:
+            b[5] |= 0x80
+        return bytes(b)
+
+    SRC = CASE + 5 + LOAD_BIAS
+    for v in (0, 1, 2, 3, 5, 7, 42, 1000, 32767, -1, -2, -42, -32768):
+        add("real_from_int(%d)" % v,
+            [s2("real_from_int", STORE + LOAD_BIAS, SRC)],
+            stdin=struct.pack("<h", v),
+            dump=6, dumpbytes=mbf(v), dumpadr=STORE,
+            note="the measured exponent bias is 129")
+
     add("wrchar('A')", [s("wrchar", ord("A"))], out=b"A")
     add("wrchar('!')", [s("wrchar", ord("!"))], out=b"!")
 

+ 52 - 9
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 57 04    mov bx,457h
+003C  BB AD 04    mov bx,4adh
 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 57 04    mov bx,457h
+00A8  BB AD 04    mov bx,4adh
 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 5C 04    mov dx,45ch
+00E6  BA B2 04    mov dx,4b2h
 00E9  EB 03       jmp 0eeh
-00EB  BA 57 04    mov dx,457h
+00EB  BA AD 04    mov dx,4adh
 00EE  B4 09       mov ah,9h
 00F0  CD 21       int 21h
 00F2  5D          pop bp
 00F3  C3          ret
-00F4  BA 65 04    mov dx,465h
+00F4  BA BB 04    mov dx,4bbh
 00F7  B4 09       mov ah,9h
 00F9  CD 21       int 21h
 00FB  C3          ret
-00FC  BA 62 04    mov dx,462h
+00FC  BA B8 04    mov dx,4b8h
 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 6B 04 mov bx,word ptr [46bh]
+021B  8B 1E C1 04 mov bx,word ptr [4c1h]
 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 6B 04 mov word ptr [46bh],bx
+0228  89 1E C1 04 mov word ptr [4c1h],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 6B 04 mov word ptr [46bh],bx
+0239  89 1E C1 04 mov word ptr [4c1h],bx
 023D  C3          ret
 023E  55          push bp
 023F  8B EC       mov bp,sp
@@ -482,3 +482,46 @@
 034B  89 EC       mov sp,bp
 034D  5D          pop bp
 034E  C3          ret
+034F  55          push bp
+0350  8B EC       mov bp,sp
+0352  53          push bx
+0353  52          push dx
+0354  56          push si
+0355  57          push di
+0356  8B 76 06    mov si,word ptr [bp+6h]
+0359  8B 04       mov ax,word ptr [si]
+035B  83 F8 00    cmp ax,0h
+035E  75 06       jne 366h
+0360  31 DB       xor bx,bx
+0362  31 D2       xor dx,dx
+0364  EB 2C       jmp 392h
+0366  88 E7       mov bh,ah
+0368  89 C2       mov dx,ax
+036A  83 FA 00    cmp dx,0h
+036D  79 02       jns 371h
+036F  F7 DA       neg dx
+0371  B8 90 00    mov ax,90h
+0374  80 FE 00    cmp dh,0h
+0377  75 04       jne 37dh
+0379  B0 88       mov al,88h
+037B  86 D6       xchg dh,dl
+037D  83 FA 00    cmp dx,0h
+0380  78 06       js 388h
+0382  FE C8       dec al
+0384  D1 E2       shl dx,1h
+0386  79 FA       jns 382h
+0388  80 FF 00    cmp bh,0h
+038B  78 03       js 390h
+038D  80 E6 7F    and dh,7fh
+0390  31 DB       xor bx,bx
+0392  8B 7E 04    mov di,word ptr [bp+4h]
+0395  89 05       mov word ptr [di],ax
+0397  89 5D 02    mov word ptr [di+2h],bx
+039A  89 55 04    mov word ptr [di+4h],dx
+039D  5F          pop di
+039E  5E          pop si
+039F  5A          pop dx
+03A0  5B          pop bx
+03A1  89 EC       mov sp,bp
+03A3  5D          pop bp
+03A4  C3          ret