Parcourir la source

calc: run-time set algebra and Succ/Pred (ladder 20912)

The CALC ladder moved 19847 -> 20912.

Runtime (entries 31..33):
  * set_op: [BP+4] = (op<<8)|size, [BP+6] = dest, [BP+8] = right,
    [BP+10] = left; op 0 AND, 1 OR, 2 AND NOT.
  * set_cmp: [BP+4] = size, [BP+6] = right, [BP+8] = left; AX carries
    equal (bit 0), strict subset (bit 1), strict superset (bit 2).
  * set_copy: [BP+4] = size, [BP+6] = source, [BP+8] = destination.

Compiler:
  * `+`, `-`, `*` over run-time sets; `=`, `<>`, `<`, `<=`, `>`, `>=`
    comparisons; and set-expression assignment.  Set operands are
    ADDRESSES; the left address is pushed before the right is parsed so a
    DI-addressed field survives.  The byte count comes from the typed set
    when one side is an inline literal (the default literal is 32 bytes,
    but CALC's `set of Attributes` field is one), and one scratch data
    buffer is allocated per expression.
  * Succ/Pred builtins (constant-folded or INC/DEC AX).
  * fixtures t85_setops and t86_succ, both executed under qemu and Exec86.

The next CALC failure is now TP3 error 98 at 20912: the compiler's fixed
4 KiB code window above the runtime has been reached.  That is the §3
layout change, not a missing construct.

Harness: the driver understands four stack words (set_op needs size,
destination and two sources); rt_exec gained set data blocks and eleven
set cases.

rt_exec 275/275; compile matrix 87/87; run_com_exec 76/76; run_exec86
76/76; ladder 1/1 at ERR 98 20912; non-vacuity 121/121; run_all ALL PASS.
Eric Streit il y a 1 jour
Parent
commit
203f77e862

+ 28 - 16
CALC-COMPILE.md

@@ -549,16 +549,15 @@ Exec86):
   byte-for-byte the same for INTEGER locals, so the compile matrix would have
   been green and only `t40_readln` (and later CALC) would have noticed.
 - **Verified baseline, run on the code HEAD above:** `./tests/run_all.sh` →
-  **OVERALL: ALL PASS**, compile matrix **85/85**, CALC ladder **1/1** at
-  `ERR 102 19847`, terminal config **4/4**, 76 `.COM` linked and **74 executed
+  **OVERALL: ALL PASS**, compile matrix **87/87**, CALC ladder **1/1** at
+  `ERR 98 20912`, terminal config **4/4**, 78 `.COM` linked and **76 executed
   under both qemu and Exec86** (`t64_arrays` remains compile-only because it
-  has no `.out`; its ~16 KB image fits the raised read).  `tests/nonvacuity.sh`:
-  **118 ok, 0 failed**.  The runtime is 3479 bytes (wrintw, real_from_int,
-  real_add, real_sub, real_mul, real_div, real_cmp, real_int, real_fmt,
-  real_val, real_write; the golden was re-blessed) and `tests/rt_exec.py`
-  passes **264/264**, including 16 add/sub, ten multiply, fourteen division,
-  thirty comparison, eighteen Int, the MBF format/parse cases and eight
-  real-output cases.  `check_realconst.py` pins the measured 6-byte Real
+  has no `.out`).  `tests/nonvacuity.sh`: **121 ok, 0 failed**.  The runtime is
+  3664 bytes (wrintw, real_from_int, real_add, real_sub, real_mul, real_div,
+  real_cmp, real_int, real_fmt, real_val, real_write, set_op, set_cmp,
+  set_copy; the golden was re-blessed) and `tests/rt_exec.py` passes
+  **275/275**, including the MBF arithmetic/output/parse cases and eleven
+  set-operation 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
@@ -754,16 +753,29 @@ Then the two that are *outside* the ladder and gate the goal as stated:
         in a comparison: `if Sheet[UFX,UFY].Contents <> '' then' pushes the
         left address before parsing the right, so DI survives; pinned by
         `t83_strfield_cmp`.  The ladder moved **18267 -> 19847**.
-      * **NEXT BLOCKER:** run-time SET ARITHMETIC.  CALC 19847 is
-        `until (Sheet[FX,FY].CellStatus * [OverWritten,Locked] = []) or ...;`
-        - the set field intersected with a literal.  Constant sets, set
-        fields, assignment and membership exist; `+`, `-`, `*`, `=`, `<>`,
-        `<=`, `>=` over run-time sets are the next rung.
+      * **DONE: run-time SET ALGEBRA** (`v-TP3-CALC-SETOPS`).  `set_op`
+        (entry 31: intersection/union/difference over `size` bytes),
+        `set_cmp` (entry 32: equality/strict-subset/strict-superset mask) and
+        `set_copy` (entry 33) are in Runtime.mod.  The compiler pushes set
+        ADDRESSES, chooses the byte count from the typed set (an inline
+        literal is the default 32-byte `set of Char`; a typed field wins and
+        the literal's first bytes are used), and allocates one scratch data
+        buffer per set expression.  `+`, `-`, `*`, `=`, `<>`, `<`, `<=`,
+        `>`, `>=` and set-expression assignment all work.  `Succ`/`Pred` were
+        the next blocker and are now builtins too.  Fixtures
+        `t85_setops`/`t86_succ`.
+      * **NEXT BLOCKER: THE §3 LAYOUT.**  CALC now reaches 20912 and fails
+        with TP3 error **98 (memory overflow)**: the compiler reserves a
+        FIXED 4 KiB code window above the runtime (`dataBase`), and CALC's own
+        code has reached it.  This is not a missing construct; code and data
+        cannot share the 64 KiB .COM segment with a static 4 KiB split.
+        Re-orienting the image (data after the actual code, or a far model)
+        is the layout change item 12, and is the next rung.
       * **STILL TO COME:** `Str`/`Val` wiring, then real expressions (a 6-byte
         temporary/value model), real comparisons and real function results.
-      * **HEADROOM:** the runtime is 3479 bytes and the probe's scaffold starts
+      * **HEADROOM:** the runtime is 3664 bytes and the probe's scaffold starts
         at HDR=0x1100 with RT_BASE=0x0190, so the ceiling is 3952 bytes - about
-        473 bytes left.  The boot read allows a .COM up to 0x6F00, so the next
+        288 bytes left.  The boot read allows a .COM up to 0x6F00, so the next
         additions can move HDR again (e.g. 0x2000) with plenty of room below
         the input at 0x7000.
       * **Exec86** needed the FE group (INC/DEC r/m8) and CMC/CLC/STC/LAHF/

+ 269 - 12
shell/Compiler.mod

@@ -120,7 +120,7 @@ CONST
       not implemented yet is a loud ENoLib, never a silent no-op. *)
    BI_ClrScr  = 6 ;  BI_GotoXY = 7 ;  BI_LowVideo = 8 ;
    BI_NormVideo = 9 ; BI_ClrEol = 10 ; BI_Delay = 11 ;
-   BI_KeyPressed = 12 ; BI_UpCase = 13 ;
+   BI_KeyPressed = 12 ; BI_UpCase = 13 ; BI_Succ = 14 ; BI_Pred = 15 ;
    (* the sub-codes EmCrtSeq takes *)
    CrtClr = 0 ; CrtDl = 1 ; CrtDh = 2 ; CrtCaret = 3 ; CrtEol = 4 ;
    CrtLow = 5 ; CrtNorm = 6 ; CrtDelay = 7 ; CrtKey = 8 ;
@@ -376,6 +376,9 @@ VAR
                                 in CX *)
    TU_StrStore : CARDINAL ;  (* s := t; dest and source addresses on the stack,
                                 limit in CX *)
+   TU_SetOp    : CARDINAL ;  (* runtime set operations, entries 31..33 *)
+   TU_SetCmp   : CARDINAL ;
+   TU_SetCopy  : CARDINAL ;
    TU_CmpStr   : CARDINAL ;  (* string comparison; two addresses on the stack,
                                 signed -1/0/+1 in AX *)
    TU_StrCat   : CARDINAL ;  (* string concatenation; dest and source addresses
@@ -2851,6 +2854,97 @@ BEGIN
    RETURN here
 END EmStrBlob ;
 
+PROCEDURE EmPushSetAddr (r : ERes) ;
+(* Push the ADDRESS of a set value: the runtime's set entries take addresses.
+   A set literal/typed constant is code-resident (kind 7), a set-expression
+   result is a data buffer (kind 8), a named set is symbol+boff, and a field
+   reached through DI is already addressed. *)
+BEGIN
+   IF r.kind = 5 THEN
+      EmPushDi ()
+   ELSIF r.kind = 1 THEN
+      EmPushVarAddr (symtab [r.idx].local,
+                     (symtab [r.idx].off + r.boff) MOD 10000H)
+   ELSIF (r.kind = 7) OR (r.kind = 8) THEN
+      EmPushVarAddr (FALSE, r.setoff)
+   ELSE
+      Err (ENoLib)
+   END
+END EmPushSetAddr ;
+
+PROCEDURE SetPairSize (left, right : ERes ; VAR size, base : CARDINAL) : BOOLEAN ;
+(* The byte count for a set operation.  An inline literal is a 32-byte
+   `set of Char' by default; when the other operand is a typed set (CALC's
+   one-byte `set of Attributes') that type wins.  Two non-literals must have
+   the same origin and are taken at the smaller size; the runtime only ever
+   touches that many bytes. *)
+BEGIN
+   IF left.setbase # right.setbase THEN
+      RETURN FALSE
+   END ;
+   base := left.setbase ;
+   IF left.kind = 7 THEN
+      IF right.kind = 7 THEN
+         size := left.setsize
+      ELSE
+         size := right.setsize
+      END
+   ELSIF right.kind = 7 THEN
+      size := left.setsize
+   ELSE
+      size := left.setsize ;
+      IF right.setsize < size THEN
+         size := right.setsize
+      END
+   END ;
+   RETURN (size > 0) AND (size <= 32)
+END SetPairSize ;
+
+PROCEDURE AllocSetScratch (size : CARDINAL) : CARDINAL ;
+(* A fresh data buffer for one set expression.  One per operation, so a nested
+   or repeated expression cannot overwrite the other side while it is still
+   being consumed.  The bytes need no initialisation: every operation writes
+   all `size' of them before the value is read. *)
+VAR off : CARDINAL ;
+BEGIN
+   IF dc + size > 0F000H THEN
+      Err (EConstRange) ;
+      RETURN 0
+   END ;
+   off := dc ;
+   dc := dc + size ;
+   RETURN off
+END AllocSetScratch ;
+
+PROCEDURE SetBinEmit (op : CARDINAL ; left, right : ERes ; VAR res : ERes) ;
+(* The caller has already pushed the LEFT address (before parsing the right
+   operand, so a DI-addressed field survives).  Finish the argument list -
+   right, destination, (op<<8)|size - call the runtime, and return the buffer
+   as a kind-8 set value. *)
+VAR size, base, dest : CARDINAL ;
+BEGIN
+   IF NOT SetPairSize (left, right, size, base) THEN
+      Err (ENoLib) ;
+      RETURN
+   END ;
+   dest := AllocSetScratch (size) ;
+   IF NOT OK () THEN
+      RETURN
+   END ;
+   EmPushSetAddr (right) ;
+   EmPushVarAddr (FALSE, dest) ;
+   EmMovAxi (W16 (op * 256 + size)) ;
+   EmPushAx () ;
+   DropC (EmCall (TU_SetOp)) ;
+   EmAddSp (8) ;
+   res.kind := 8 ;
+   res.cls := TSet ;
+   res.chr := FALSE ;
+   res.setoff := dest ;
+   res.setbase := base ;
+   res.setsize := size
+END SetBinEmit ;
+
 PROCEDURE EmPushStrAddr (r : ERes) ;
 (* Push the ADDRESS of a whole-string operand - the argument shape cmpstr
    takes, one per operand.
@@ -3359,6 +3453,7 @@ PROCEDURE ParseCmp (VAR r : ERes) ;
 VAR op : CARDINAL ;
     left, right : ERes ;
     f : LONGINT ;
+    size, base : CARDINAL ;
 BEGIN
    ParseAdd (r) ;
    LOOP
@@ -3394,7 +3489,42 @@ BEGIN
          RETURN
       END ;
       left := r ;
-      IF left.cls = TString THEN
+      IF left.cls = TSet THEN
+         (* Set comparison.  set_cmp returns a mask (bit 0 equal, bit 1 strict
+            subset, bit 2 strict superset); the operator selects which bits
+            answer it, and the shared EmRelSetcc arm turns the flags into a
+            boolean.  The left address is pushed before the right operand is
+            parsed, for the same DI reason as strings and set arithmetic. *)
+         EmPushSetAddr (left) ;
+         ParseAdd (right) ;
+         IF NOT OK () THEN
+            RETURN
+         END ;
+         IF right.cls # TSet THEN
+            Err (ETypeErr) ;
+            RETURN
+         END ;
+         IF NOT SetPairSize (left, right, size, base) THEN
+            Err (ENoLib) ;
+            RETURN
+         END ;
+         EmPushSetAddr (right) ;
+         EmMovAxi (size) ;
+         EmPushAx () ;
+         DropC (EmCall (TU_SetCmp)) ;
+         EmAddSp (4) ;
+         CASE op OF
+            1 : EmMovCxi (1) ; EmAndAxCx () ; EmOrAxAx () ; EmRelSetcc (2) ;
+         |  2 : EmMovCxi (1) ; EmAndAxCx () ; EmXorAl01 () ;
+               EmOrAxAx () ; EmRelSetcc (2) ;
+         |  3 : EmMovCxi (2) ; EmAndAxCx () ; EmOrAxAx () ; EmRelSetcc (2) ;
+         |  4 : EmMovCxi (4) ; EmAndAxCx () ; EmOrAxAx () ; EmRelSetcc (2) ;
+         |  5 : EmMovCxi (5) ; EmAndAxCx () ; EmOrAxAx () ; EmRelSetcc (2) ;
+         |  6 : EmMovCxi (3) ; EmAndAxCx () ; EmOrAxAx () ; EmRelSetcc (2)
+         END ;
+         r.kind := 2 ;
+         r.cls := TBool
+      ELSIF left.cls = TString THEN
          (* A string comparison: cmpstr, the entry TPSRC9's vcompare (133)
             reaches through TPSRC2's csteq/cstne/cstge/cstle/cstg/cstl
             wrappers (890-925).
@@ -3430,8 +3560,8 @@ BEGIN
       ELSE
          SaveLeft (left) ;
          ParseAdd (right) ;
-         IF right.cls = TString THEN
-            Err (ETypeErr) ;         (* a number compared with a string *)
+         IF (right.cls = TString) OR (right.cls = TSet) THEN
+            Err (ETypeErr) ;         (* a number compared with a string/set *)
             RETURN
          END ;
          IF (left.kind = 0) AND (right.kind = 0) THEN
@@ -3474,9 +3604,41 @@ BEGIN
          RETURN
       END ;
       left := r ;
-      SaveLeft (left) ;
-      ParseMul (right) ;
-      BinOpEmit (op, left, right, r)
+      IF left.cls = TSet THEN
+         (* Set union/difference.  The left address goes on the stack BEFORE
+            the right is parsed: a DI-addressed set field must survive the
+            right-hand expression.  `OR' is boolean, not set union. *)
+         IF (op = OpAdd) OR (op = OpSub) THEN
+            EmPushSetAddr (left) ;
+            ParseMul (right) ;
+            IF NOT OK () THEN
+               RETURN
+            END ;
+            IF right.cls # TSet THEN
+               Err (ETypeErr) ;
+               RETURN
+            END ;
+            IF op = OpAdd THEN
+               SetBinEmit (1, left, right, r)
+            ELSE
+               SetBinEmit (2, left, right, r)
+            END
+         ELSE
+            Err (ETypeErr) ;
+            RETURN
+         END
+      ELSE
+         SaveLeft (left) ;
+         ParseMul (right) ;
+         IF NOT OK () THEN
+            RETURN
+         END ;
+         IF right.cls = TSet THEN
+            Err (ETypeErr) ;
+            RETURN
+         END ;
+         BinOpEmit (op, left, right, r)
+      END
    END
 END ParseAdd ;
 
@@ -3505,9 +3667,35 @@ BEGIN
          RETURN
       END ;
       left := r ;
-      SaveLeft (left) ;
-      ParseNeg (right) ;
-      BinOpEmit (op, left, right, r)
+      IF left.cls = TSet THEN
+         (* Set intersection.  Same push-before-the-right rule as ParseAdd. *)
+         IF op = OpMul THEN
+            EmPushSetAddr (left) ;
+            ParseNeg (right) ;
+            IF NOT OK () THEN
+               RETURN
+            END ;
+            IF right.cls # TSet THEN
+               Err (ETypeErr) ;
+               RETURN
+            END ;
+            SetBinEmit (0, left, right, r)
+         ELSE
+            Err (ETypeErr) ;
+            RETURN
+         END
+      ELSE
+         SaveLeft (left) ;
+         ParseNeg (right) ;
+         IF NOT OK () THEN
+            RETURN
+         END ;
+         IF right.cls = TSet THEN
+            Err (ETypeErr) ;
+            RETURN
+         END ;
+         BinOpEmit (op, left, right, r)
+      END
    END
 END ParseMul ;
 
@@ -3748,6 +3936,42 @@ BEGIN
       r.cls := TChar ;
       r.chr := FALSE ;
       RETURN
+   ELSIF (symtab [idx].tag = KBuiltin) AND
+         ((symtab [idx].cls = BI_Succ) OR (symtab [idx].cls = BI_Pred)) THEN
+      (* Succ(x) / Pred(x) on an ordinal.  CALC's `FX := Succ(FX)' is the
+         first use.  A constant folds; otherwise load the value and INC/DEC
+         it, preserving the operand's class so an assignment back to a CHAR
+         subrange stores one byte as before. *)
+      IF NOT MatchDelim ('(') THEN
+         Err (ENoSemi) ;
+         RETURN
+      END ;
+      ParseExpr (r) ;
+      ExpectDelim (')', ENoSemi) ;
+      IF NOT OK () THEN
+         RETURN
+      END ;
+      IF (r.cls # TChar) AND (r.cls # TScalar) THEN
+         Err (ETypeErr) ;
+         RETURN
+      END ;
+      IF r.kind = 0 THEN
+         IF symtab [idx].cls = BI_Succ THEN
+            r.imm := VAL (LONGINT, W16 (r.imm + 1))
+         ELSE
+            r.imm := VAL (LONGINT, W16 (r.imm - 1))
+         END
+      ELSE
+         LoadAtom (r) ;
+         IF symtab [idx].cls = BI_Succ THEN
+            EmIncAx ()
+         ELSE
+            EmDecAx ()
+         END ;
+         r.kind := 2
+      END ;
+      r.chr := FALSE ;
+      RETURN
    ELSIF (symtab [idx].tag = KBuiltin) AND (symtab [idx].cls = BI_KeyPressed) THEN
       (* KeyPressed: a boolean FUNCTION with no argument, and TP3 lets it be
          written without a parameter list (`until KeyPressed'). *)
@@ -4180,8 +4404,36 @@ BEGIN
       END ;
       Skip () ;
       IF CurCh () # '[' THEN
-         (* set := set, or a set expression: the next increment *)
-         Err (ENoLib) ;
+         (* `set := set' or a set expression.  The destination is a variable
+            or a DI-addressed field; save the DI address BEFORE the right-hand
+            expression can reuse DI, then copy the runtime's bytes. *)
+         IF r.kind = 5 THEN
+            EmPushDi ()
+         END ;
+         ParseExpr (src) ;
+         IF NOT OK () THEN
+            RETURN
+         END ;
+         IF src.cls # TSet THEN
+            Err (ETypeErr) ;
+            RETURN
+         END ;
+         IF r.kind = 1 THEN
+            EmPushVarAddr (symtab [r.idx].local,
+                           (symtab [r.idx].off + r.boff) MOD 10000H)
+         ELSIF r.kind # 5 THEN
+            Err (ENoLib) ;
+            RETURN
+         END ;
+         EmPushSetAddr (src) ;
+         n := r.setsize ;
+         IF (src.kind = 8) AND (src.setsize < n) THEN
+            n := src.setsize
+         END ;
+         EmMovAxi (n) ;
+         EmPushAx () ;
+         DropC (EmCall (TU_SetCopy)) ;
+         EmAddSp (4) ;
          RETURN
       END ;
       IF NOT RdSetLiteral (r.setbase, r.setsize, sbuf) THEN
@@ -6356,6 +6608,8 @@ BEGIN
    i := NewSym ("KEYPRESSED", KBuiltin, BI_KeyPressed, 0, 0, 0, 0, FALSE) ;
    symtab [i].defnd := TRUE ;
    i := NewSym ("UPCASE", KBuiltin, BI_UpCase, 0, 0, 0, 0, FALSE) ;
+   i := NewSym ("SUCC", KBuiltin, BI_Succ, 0, 0, 0, 0, FALSE) ;
+   i := NewSym ("PRED", KBuiltin, BI_Pred, 0, 0, 0, 0, FALSE) ;
    symtab [i].defnd := TRUE ;
    (* KBD: the console keyboard as a TEXT file.  This runtime's read entries
       already read the console, so `Read(Kbd, Ch)' drops the file argument and
@@ -6510,6 +6764,9 @@ BEGIN
    TU_Halt     := RT_Entry (12) ;
    TU_WrInl    := RT_Entry (13) ;      (* inline string literal *)
    TU_RdByte   := RT_Entry (14) ;      (* a BYTE read stores one byte *)
+   TU_SetOp    := RT_Entry (31) ;
+   TU_SetCmp   := RT_Entry (32) ;
+   TU_SetCopy  := RT_Entry (33) ;
    TU_WrStr    := RT_Entry (15) ;      (* a string variable is written through
                                           the address on the stack *)
    TU_RdStr    := RT_Entry (16) ;      (* readln(s)/read(s): TPSRC8 prdstr,

+ 7 - 0
shell/Runtime.def

@@ -64,6 +64,13 @@ CONST
    E_RealVal = 29 ;         (* ascreal: parse an ASCIIZ string into a real *)
    E_RealWrite = 30 ;       (* fmtreal + padding + console output:
                                [BP+4]=decimals, [BP+6]=width, [BP+8]=real *)
+   E_SetOp   = 31 ;         (* set op: [BP+4]=packed(op,size), [BP+6]=dest,
+                               [BP+8]=right, [BP+10]=left *)
+   E_SetCmp  = 32 ;         (* set compare: [BP+4]=size, [BP+6]=right,
+                               [BP+8]=left; AX = bit0 equal, bit1 subset,
+                               bit2 superset *)
+   E_SetCopy = 33 ;         (* set copy: [BP+4]=size, [BP+6]=src,
+                               [BP+8]=dest *)
 
    (* 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

+ 128 - 4
shell/Runtime.mod

@@ -149,7 +149,7 @@ VAR
    dataAt : CARDINAL ;
    rtBase : CARDINAL ;
    built  : BOOLEAN ;
-   entNm  : ARRAY [0..30] OF ARRAY [0..MaxNm] OF CHAR ;
+   entNm  : ARRAY [0..33] OF ARRAY [0..MaxNm] OF CHAR ;
 
 (* ---------------------------------------------------------------- *)
 (*  name helpers                                                     *)
@@ -382,7 +382,6 @@ PROCEDURE XorDiDi ; BEGIN B (31H) ; B (0FFH) END XorDiDi ;  (* 11 111 111 *)
 
 PROCEDURE IncCx ; BEGIN B (41H) END IncCx ;
 PROCEDURE IncSi ; BEGIN B (46H) END IncSi ;
-PROCEDURE IncDi ; BEGIN B (47H) END IncDi ;
 PROCEDURE DecSi ; BEGIN B (4EH) END DecSi ;
 PROCEDURE AddDi2 ; BEGIN B (83H) ; B (0C7H) ; B (2) END AddDi2 ;  (* 11 000 111 *)
 
@@ -806,6 +805,31 @@ PROCEDURE MovDiBp6 ; BEGIN B (8BH) ; B (7EH) ; B (6) END MovDiBp6 ;
 PROCEDURE MovDxBp6 ; BEGIN B (8BH) ; B (56H) ; B (6) END MovDxBp6 ;
 PROCEDURE PushBp6 ; BEGIN B (0FFH) ; B (76H) ; B (6) END PushBp6 ;
 PROCEDURE MovCxBp6 ; BEGIN B (8BH) ; B (4EH) ; B (6) END MovCxBp6 ;
+PROCEDURE MovCxBp4 ; BEGIN B (8BH) ; B (4EH) ; B (4) END MovCxBp4 ;
+PROCEDURE MovBxBp8 ; BEGIN B (8BH) ; B (5EH) ; B (8) END MovBxBp8 ;
+PROCEDURE MovSiBp8 ; BEGIN B (8BH) ; B (76H) ; B (8) END MovSiBp8 ;
+PROCEDURE MovSiBp10 ; BEGIN B (8BH) ; B (76H) ; B (0AH) END MovSiBp10 ;
+PROCEDURE LdDlDi ; BEGIN B (8AH) ; B (15H) END LdDlDi ;          (* DL:=[DI] *)
+PROCEDURE AndAlDl ; BEGIN B (20H) ; B (0D0H) END AndAlDl ;       (* AL:=AL AND DL *)
+PROCEDURE OrAlDl ; BEGIN B (08H) ; B (0D0H) END OrAlDl ;         (* AL:=AL OR DL *)
+PROCEDURE NotDl ; BEGIN B (0F6H) ; B (0D2H) END NotDl ;          (* NOT DL *)
+PROCEDURE NotAl ; BEGIN B (0F6H) ; B (0D0H) END NotAl ;          (* NOT AL *)
+PROCEDURE MovBlAl ; BEGIN B (88H) ; B (0C3H) END MovBlAl ;       (* BL:=AL *)
+PROCEDURE AndAlDh ; BEGIN B (20H) ; B (0F0H) END AndAlDh ;       (* AL:=AL AND DH *)
+PROCEDURE CmpBlDh ; BEGIN B (38H) ; B (0F3H) END CmpBlDh ;       (* CMP BL,DH *)
+PROCEDURE AndAxImm (v : CARDINAL) ;
+BEGIN B (25H) ; W (v) END AndAxImm ;                             (* AND AX,imm16 *)
+PROCEDURE TestAxImm (v : CARDINAL) ;
+BEGIN B (0A9H) ; W (v) END TestAxImm ;                           (* TEST AX,imm16 *)
+PROCEDURE AndAhImm (v : CARDINAL) ;
+BEGIN B (80H) ; B (0E4H) ; B (v MOD 256) END AndAhImm ;          (* AND AH,imm8 *)
+PROCEDURE TestAhImm (v : CARDINAL) ;
+BEGIN B (0F6H) ; B (0C4H) ; B (v MOD 256) END TestAhImm ;        (* TEST AH,imm8 *)
+PROCEDURE MovAlAh ; BEGIN B (88H) ; B (0E0H) END MovAlAh ;       (* AL:=AH *)
+PROCEDURE CmpAhImm (v : CARDINAL) ;
+BEGIN B (80H) ; B (0FCH) ; B (v MOD 256) END CmpAhImm ;          (* CMP AH,imm8 *)
+PROCEDURE DecDh ; BEGIN B (0FEH) ; B (0CEH) END DecDh ;          (* DEC DH *)
+PROCEDURE IncDi ; BEGIN B (47H) END IncDi ;
 PROCEDURE MovDxBp4 ; BEGIN B (8BH) ; B (56H) ; B (4) END MovDxBp4 ;
 PROCEDURE MovDiBp8 ; BEGIN B (8BH) ; B (7EH) ; B (8) END MovDiBp8 ;
 PROCEDURE SbbBxData (delta : CARDINAL) ;
@@ -2780,6 +2804,103 @@ BEGIN
    MovSpBp ; PopBp ; RetR
 END EmitRealConv ;
 
+PROCEDURE EmitSetOps ;
+(* Set algebra over byte arrays.  A set VALUE is an address plus a length;
+   the compiler picks the length from the declared set type.  All three
+   entries take the length on the stack, so no register state survives a
+   call except what the entry itself preserves.
+
+   set_op:  [BP+4] = (op << 8) | size, [BP+6] = dest, [BP+8] = right,
+            [BP+10] = left.  op 0 = AND (intersection), 1 = OR (union),
+            2 = left AND NOT right (difference).
+   set_cmp: [BP+4] = size, [BP+6] = right, [BP+8] = left.  AX = mask:
+            bit 0 = equal, bit 1 = strict subset, bit 2 = strict superset.
+   set_copy: [BP+4] = size, [BP+6] = source, [BP+8] = destination. *)
+BEGIN
+   M ("set_op") ;
+   PushBp ; MovBpSp ;
+   PushBx ; PushCx ; PushDx ; PushSi ; PushDi ;
+   MovAxBp4 ;                       (* AH = op, AL = size *)
+   MovDhAl ;                        (* DH = byte counter *)
+   MovSiBp10 ;                      (* SI = left *)
+   MovDiBp8 ;                       (* DI = right *)
+   MovBxBp6 ;                       (* BX = destination *)
+   CmpDh0 ;
+   Je8 ("so_done") ;
+   M ("so_loop") ;
+   LdAlSi ; LdDlDi ;
+   CmpAhImm (0) ; Je8 ("so_and") ;
+   CmpAhImm (1) ; Je8 ("so_or") ;
+   NotDl ; AndAlDl ;                (* difference *)
+   Jmp8 ("so_store") ;
+   M ("so_and") ;
+   AndAlDl ;
+   Jmp8 ("so_store") ;
+   M ("so_or") ;
+   OrAlDl ;
+   M ("so_store") ;
+   StBxAl ;
+   IncSi ; IncDi ; IncBx ;
+   DecDh ;
+   Jne8 ("so_loop") ;
+   M ("so_done") ;
+   PopDi ; PopSi ; PopDx ; PopCx ; PopBx ;
+   MovSpBp ; PopBp ; RetR ;
+
+   M ("set_cmp") ;
+   PushBp ; MovBpSp ;
+   PushBx ; PushCx ; PushDx ; PushSi ; PushDi ;
+   MovCxBp4 ;                       (* CX = size *)
+   MovSiBp8 ;                       (* left *)
+   MovDiBp6 ;                       (* right *)
+   MovAh (7) ;                      (* equal | subset | superset; AH, not AX:
+                                       AL is the set byte under test *)
+   Jcxz8 ("scm_final") ;
+   M ("scm_loop") ;
+   LdAlSi ; LdDlDi ;
+   MovBlAl ; MovDhDl ;              (* keep the originals *)
+   CmpAlDl ;
+   Je8 ("scm_eq") ;
+   AndAhImm (0FEH) ;                (* not equal *)
+   M ("scm_eq") ;
+   NotDl ; AndAlDl ;                (* left AND NOT right *)
+   CmpAl0 ;
+   Je8 ("scm_sub") ;
+   AndAhImm (0FDH) ;                (* not a subset *)
+   M ("scm_sub") ;
+   MovAlBl ; NotAl ; AndAlDh ;      (* right AND NOT left *)
+   CmpAl0 ;
+   Je8 ("scm_sup") ;
+   AndAhImm (0FBH) ;                (* not a superset *)
+   M ("scm_sup") ;
+   IncSi ; IncDi ;
+   Loop8 ("scm_loop") ;
+   (* equality is not a STRICT subset/superset *)
+   M ("scm_final") ;
+   TestAhImm (1) ;
+   Je8 ("scm_done") ;
+   AndAhImm (0F9H) ;
+   M ("scm_done") ;
+   MovAlAh ;                        (* AX := the mask *)
+   MovAh (0) ;
+   PopDi ; PopSi ; PopDx ; PopCx ; PopBx ;
+   MovSpBp ; PopBp ; RetR ;
+
+   M ("set_copy") ;
+   PushBp ; MovBpSp ;
+   PushBx ; PushCx ; PushSi ;
+   MovCxBp4 ;
+   MovSiBp6 ;
+   MovBxBp8 ;
+   Jcxz8 ("scp_done") ;
+   M ("scp_loop") ;
+   LdAlSi ; StBxAl ; IncSi ; IncBx ;
+   Loop8 ("scp_loop") ;
+   M ("scp_done") ;
+   PopSi ; PopCx ; PopBx ;
+   MovSpBp ; PopBp ; RetR
+END EmitSetOps ;
+
 
 PROCEDURE EmitData ;
 (* The runtime's data block.  The D_ constants name the offsets in it and are
@@ -2983,7 +3104,7 @@ BEGIN
    EmitGetCh ; EmitUnGetCh ;
    EmitRdStr ; EmitStrStore ; EmitCmpStr ; EmitStrCat ;
    EmitRealFromInt ; EmitRealAddSub ; EmitRealMul ;
-   EmitRealCmp ; EmitRealInt ; EmitRealConv ;
+   EmitRealCmp ; EmitRealInt ; EmitRealConv ; EmitSetOps ;
    EmitData ;
    FixUp ;
    SetStr (entNm [0], "initmem") ;
@@ -3017,6 +3138,9 @@ BEGIN
    SetStr (entNm [28], "real_fmt") ;
    SetStr (entNm [29], "real_val") ;
    SetStr (entNm [30], "real_write") ;
+   SetStr (entNm [31], "set_op") ;
+   SetStr (entNm [32], "set_cmp") ;
+   SetStr (entNm [33], "set_copy") ;
    built := TRUE
 END RT_Build ;
 
@@ -3044,7 +3168,7 @@ BEGIN
    IF NOT built THEN
       RT_Build (0)
    END ;
-   IF i > 30 THEN
+   IF i > 33 THEN
       RETURN 0
    END ;
    RETURN rtBase + LblOff (entNm [i])

+ 6 - 3
shell/tests/RtProbe.mod

@@ -108,7 +108,7 @@ END PENT ;
 
 PROCEDURE Main ;
 VAR i, n, base : CARDINAL ;
-    names : ARRAY [0..30] OF ARRAY [0..15] OF CHAR ;
+    names : ARRAY [0..33] 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 <= 30 DO
+   WHILE i <= 33 DO
       names [i] := "" ;
       INC (i)
    END ;
@@ -143,8 +143,11 @@ BEGIN
    names [28] := "real_fmt" ;
    names [29] := "real_val" ;
    names [30] := "real_write" ;
+   names [31] := "set_op" ;
+   names [32] := "set_cmp" ;
+   names [33] := "set_copy" ;
    i := 0 ;
-   WHILE i <= 30 DO
+   WHILE i <= 33 DO
       PENT (i, names [i]) ;
       INC (i)
    END ;

+ 2 - 1
shell/tests/audit_helpers.py

@@ -146,7 +146,8 @@ NON_ONE_LINE = {
         "AddAxImm": COMP, "AddChImm": COMP, "AddClImm": COMP,
         "AndBlImm": COMP, "AndClImm": COMP, "CmpAxImm": COMP,
         "CmpBxByte": COMP, "CmpDxImm": COMP, "CmpSiData": COMP,
-        "DecData": COMP,
+        "DecData": COMP, "AndAhImm": COMP, "AndAxImm": COMP,
+        "CmpAhImm": COMP, "TestAhImm": COMP, "TestAxImm": COMP,
         "SubAl": PARAM, "CmpAl": PARAM, "AddDl": PARAM, "J8": COMP,
         "Jcc": COMP, "CmpCxV": PARAM,
         "B": DATA, "C8": DATA,

+ 1 - 1
shell/tests/check_8086.py

@@ -182,7 +182,7 @@ def probe_runtime():
 # listed rather than detected, and the runtime sweep (the backbone) plus the
 # execution oracles still cover the emitted instructions.
 CODE_RESIDENT_CONST = {"t61_setconst", "t64_arrays",
-                       "t49_strcmp_lit", "t83_strfield_cmp"}
+                       "t49_strcmp_lit", "t83_strfield_cmp", "t85_setops"}
 # The last two emit string literals INLINE in the code stream
 # (`JMP over <len><chars>`): the bytes after the jump are data, and a linear
 # sweep that does not follow the jump decodes them as instructions.  The

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

@@ -151,6 +151,13 @@
 	.set	R_BLK34, 0x1520
 	.set	R_BLK35, 0x1530
 	.set	R_BLK36, 0x1540
+	.set	SETA, 0x1580
+	.set	SETB, 0x1590
+	.set	SETC, 0x15A0
+	.set	SETD, 0x15B0
+	.set	SET2A, 0x15C0
+	.set	SET2B, 0x15D0
+	.set	SET2D, 0x15E0
 
 	.code16
 	.text
@@ -274,6 +281,8 @@ drive:
 # The flags are re-read after the call because the entry has just used AX.
 doslot:
 	movb	4(%bp), %al
+	testb	$64, %al
+	jnz	.Lfourstack
 	testb	$32, %al
 	jnz	.Lthreestack
 	testb	$8, %al
@@ -290,6 +299,12 @@ doslot:
 	pushw	8(%bp)			# [BP+8]
 	pushw	6(%bp)			# [BP+6]
 	pushw	2(%bp)			# [BP+4]
+	jmp	.Lnostack
+.Lfourstack:
+	pushw	10(%bp)			# [BP+10]
+	pushw	8(%bp)			# [BP+8]
+	pushw	6(%bp)			# [BP+6]
+	pushw	2(%bp)			# [BP+4]
 .Lnostack:
 	testb	$2, %al
 	jz	.Lnoax
@@ -306,6 +321,8 @@ doslot:
 	call	*%si
 	movw	%ax, D_AX + LOAD_BIAS	# observable return value
 	movb	4(%bp), %al
+	testb	$64, %al
+	jnz	.Lcleanfour
 	testb	$32, %al
 	jnz	.Lcleanthree
 	testb	$8, %al
@@ -321,6 +338,9 @@ doslot:
 .Lcleanthree:
 	addw	$6, %sp
 	ret
+.Lcleanfour:
+	addw	$8, %sp
+	ret
 
 # wrtinl's argument is NOT a stack word: the entry reads a length byte and that
 # many characters from its own return address.  That is the caller's half of
@@ -581,3 +601,19 @@ HDR:
 
 	.org	R_BLK36
 	.byte	0x8E, 0x00, 0x00, 0x00, 0xD0, 0x40, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00
+
+# Set buffers for set_op/set_cmp/set_copy.
+	.org	SETA
+	.byte	0x0A, 0x00, 0x00, 0x00
+	.org	SETB
+	.byte	0x18, 0x00, 0x00, 0x00
+	.org	SETC
+	.byte	0x02, 0x00, 0x00, 0x00
+	.org	SETD
+	.byte	0x00, 0x00, 0x00, 0x00
+	.org	SET2A
+	.byte	0x0A, 0x0F
+	.org	SET2B
+	.byte	0x18, 0x03
+	.org	SET2D
+	.byte	0x00, 0x00

+ 7 - 6
shell/tests/fixtures/calc_ladder.tsv

@@ -28,9 +28,10 @@
 # Current rung (2026-10-21): `Write(Value:FW:DEC)' in CALC.INC's GotoCell - a
 # REAL write with a field width and decimals.  UpCase (t81_upcase), run-time
 # set membership (t82_setin) and a string field written through DI (t79
-# extended) moved the ladder 15911 -> 18267 -> 19847.  REAL output now works
-# (`Write(Value:FW:DEC)`, `Write(Value:FW)` and the default width), and a
-# string field may be compared before the right operand is parsed.  The blocker
-# is now run-time SET ARITHMETIC:
-#   until (Sheet[FX,FY].CellStatus * [OverWritten,Locked] = []) or ...;
-CALC.PAS	ERR	102	19847
+# extended) moved the ladder 15911 -> 18267 -> 19847 -> 20912.  Real output,
+# string comparisons, run-time set algebra (`*`, `+`, `-`, set comparisons and
+# set-expression assignment) and Succ/Pred now work.  The blocker is now the
+# LAYOUT limit: the compiler reserves a fixed 4 KiB code window above the
+# runtime, and CALC's own code has reached it (TP3's error 98, pos 20912).
+# That is the §3 layout change, not a missing construct.
+CALC.PAS	ERR	98	20912

+ 4 - 0
shell/tests/fixtures/expected.tsv

@@ -616,3 +616,7 @@ t82_setin	OK	1052	26
 #   and Write(real:width:decimals) with both formats driven by variables.
 t83_strfield_cmp	OK	159	29
 t84_realwrite	OK	160	15
+
+#   t85/t86 are the run-time-set rung and the Succ/Pred builtins.
+t85_setops	OK	1355	17
+t86_succ	OK	115	8

+ 7 - 0
shell/tests/fixtures/t85_setops.out

@@ -0,0 +1,7 @@
+inter
+eq
+leftlit
+sup
+sub
+union
+diff

+ 23 - 0
shell/tests/fixtures/t85_setops.pas

@@ -0,0 +1,23 @@
+PROGRAM T85;
+TYPE
+   Attr = (A, B, C, D, E) ;
+   AttrSet = SET OF Attr ;
+   Rec = RECORD
+      Status : AttrSet ;
+   END ;
+VAR
+   M : ARRAY [1..2, 1..2] OF Rec ;
+   I, J : Integer ;
+BEGIN
+   I := 1 ; J := 1 ;
+   M [I,J].Status := [B,D] ;
+   IF M [I,J].Status * [C,D] = [D] THEN WRITELN ('inter') ELSE WRITELN ('bad1') ;
+   IF M [I,J].Status = [B,D] THEN WRITELN ('eq') ELSE WRITELN ('bad2') ;
+   IF [B,D] * M [I,J].Status = [B,D] THEN WRITELN ('leftlit') ELSE WRITELN ('bad3') ;
+   IF M [I,J].Status >= [B] THEN WRITELN ('sup') ELSE WRITELN ('bad4') ;
+   IF [B] <= M [I,J].Status THEN WRITELN ('sub') ELSE WRITELN ('bad5') ;
+   M [I,J].Status := M [I,J].Status + [C] ;
+   IF M [I,J].Status = [B,C,D] THEN WRITELN ('union') ELSE WRITELN ('bad6') ;
+   M [I,J].Status := M [I,J].Status - [B] ;
+   IF M [I,J].Status = [C,D] THEN WRITELN ('diff') ELSE WRITELN ('bad7')
+END.

+ 3 - 0
shell/tests/fixtures/t86_succ.out

@@ -0,0 +1,3 @@
+B
+4
+B

+ 14 - 0
shell/tests/fixtures/t86_succ.pas

@@ -0,0 +1,14 @@
+PROGRAM T86;
+VAR
+   C : Char ;
+   N : Integer ;
+BEGIN
+   C := 'A' ;
+   C := Succ (C) ;
+   WRITELN (C) ;
+   N := 5 ;
+   N := Pred (N) ;
+   WRITELN (N) ;
+   C := Pred (Succ (C)) ;
+   WRITELN (C)
+END.

+ 66 - 5
shell/tests/nonvacuity.sh

@@ -2268,11 +2268,11 @@ cp "$SAVED_C" Compiler.mod
 if python3 - <<'PYX'
 p = 'Compiler.mod'
 s = open(p).read()
-old = """      EmPushDi ()
-   ELSIF r.kind = 1 THEN"""
-assert s.count(old) == 1, 'the kind-5 string push found %d times' % s.count(old)
-open(p, 'w').write(s.replace(old, """      Err (ENoLib)
-   ELSIF r.kind = 1 THEN"""))
+old = """   IF r.kind = 5 THEN
+      (* a string element/field computed through DI - `Sheet[X,Y].Contents'"""
+assert s.count(old) == 1, 'the kind-5 string branch found %d times' % s.count(old)
+open(p, 'w').write(s.replace(old, """   IF r.kind = 4 THEN
+      (* a string element/field computed through DI - `Sheet[X,Y].Contents'"""))
 PYX
 then
     if rebuild_compiler; then
@@ -2304,6 +2304,67 @@ fi
 cp "$SAVED" Runtime.mod
 "$GM2" -fiso -c Runtime.mod >/dev/null 2>&1
 
+# --- set_op's intersection really ANDs the two bytes ---------------------
+# 20 D0 is AND AL,DL; 20 C2 is AND DL,AL and leaves AL (the left byte), which
+# is exactly the bug this rung fixed.  The exact-byte set_op cases catch it.
+cp "$SAVED" Runtime.mod
+if mutate Runtime.mod 's|PROCEDURE AndAlDl ; BEGIN B (20H) ; B (0D0H)|PROCEDURE AndAlDl ; BEGIN B (20H) ; B (0C2H)|'; then
+    if "$GM2" -fiso -c Runtime.mod >/dev/null 2>&1; then
+        expect_red "set_op red when intersection uses the wrong AND operand" \
+            "set_op" python3 tests/rt_exec.py
+    else
+        echo "  FAIL: the runtime would not rebuild with the AND mutation"
+        fail=$((fail + 1))
+    fi
+fi
+cp "$SAVED" Runtime.mod
+"$GM2" -fiso -c Runtime.mod >/dev/null 2>&1
+
+# --- set union is OR, not intersection -----------------------------------
+# t85's `union' line is `Status := Status + [C]'; choosing intersection there
+# loses C (and the later difference line too).  This pins the compiler's
+# operator-to-runtime mapping, not just the runtime core.
+cp "$SAVED_C" Compiler.mod
+if mutate_compiler 's|               SetBinEmit (1, left, right, r)|               SetBinEmit (0, left, right, r)|'; then
+    if rebuild_compiler; then
+        expect_red "t85 red when set addition becomes intersection" \
+            "t85_setops" python3 tests/run_com_exec.py t85_setops
+    else
+        echo "  FAIL: the compiler would not rebuild with set + as *"
+        fail=$((fail + 1))
+    fi
+fi
+cp "$SAVED_C" Compiler.mod
+
+# --- Succ increments, Pred decrements ------------------------------------
+cp "$SAVED_C" Compiler.mod
+if python3 - <<'PYX'
+p = 'Compiler.mod'
+s = open(p).read()
+old = """         IF symtab [idx].cls = BI_Succ THEN
+            EmIncAx ()
+         ELSE
+            EmDecAx ()"""
+assert s.count(old) == 1, 'the Succ branch found %d times' % s.count(old)
+open(p, 'w').write(s.replace(old, """         IF symtab [idx].cls = BI_Succ THEN
+            EmDecAx ()
+         ELSE
+            EmDecAx ()"""))
+PYX
+then
+    if rebuild_compiler; then
+        expect_red "t86 red when Succ decrements" \
+            "t86_succ" python3 tests/run_com_exec.py t86_succ
+    else
+        echo "  FAIL: the compiler would not rebuild with Succ as Pred"
+        fail=$((fail + 1))
+    fi
+else
+    echo "  BROKEN CASE: the Succ mutation did not apply"
+    fail=$((fail + 1))
+fi
+cp "$SAVED_C" Compiler.mod
+
 if rebuild_compiler; then
     if python3 tests/run_com_exec.py >/dev/null 2>&1; then
         echo "  ok: every executed fixture passes on the restored sources"

+ 48 - 10
shell/tests/rt_exec.py

@@ -110,7 +110,7 @@ ENTS = ["initmem", "progend", "stackchk", "wrint", "wrchar", "wrbool",
         "wrtinl", "rdbyte", "wrstr", "rdstr", "strstore", "cmpstr", "strcat",
         "wrintw", "real_from_int", "real_add", "real_sub", "real_mul",
         "real_div", "real_cmp", "real_int", "real_fmt", "real_val",
-        "real_write"]
+        "real_write", "set_op", "set_cmp", "set_copy"]
 
 # A call slot's flags, as rtdrv.s reads them.
 F_STACK = 1     # the argument goes on the stack, popped by the caller
@@ -122,6 +122,7 @@ F_CX = 16       # CX := the word at +8, the maximum length rdstr and strstore
                 # both read from the register
 F_STACK3 = 32   # three stack words: +8 first, then +6, then +2, so +2 is
                 # [BP+4], +6 is [BP+6] and +8 is [BP+8]
+F_STACK4 = 64   # four stack words: +10, +8, +6, +2 -> [BP+10]..[BP+4]
 
 # The byte patterns check_bp_contract looks for, and the rule they express:
 # "an entry that borrows BP opens with PUSH BP; MOV BP,SP and pops it again
@@ -324,13 +325,14 @@ def case_bytes(c, entries):
         sys.exit("case %s has %d calls; the record holds 4"
                  % (c["name"], len(calls)))
     b[15] = len(calls)                                  # OFF_NCALLS (byte)
-    for i, (ent, arg, mode, arg2, cx) in enumerate(calls):
+    for i, (ent, arg, mode, arg2, cx, arg3) in enumerate(calls):
         o = 16 + 12 * i                                 # SLOT0, SLOT_SZ = 12
         struct.pack_into("<H", b, o, entries[ent])
         struct.pack_into("<H", b, o + 2, arg & 0xFFFF)
         b[o + 4] = mode
         struct.pack_into("<H", b, o + 6, arg2 & 0xFFFF)
         struct.pack_into("<H", b, o + 8, cx & 0xFFFF)
+        struct.pack_into("<H", b, o + 10, arg3 & 0xFFFF)
     return bytes(b)
 
 
@@ -435,36 +437,41 @@ def build_cases():
                    "note": note, "index": len(cs)})
 
     def s(ent, arg):
-        return (ent, arg, F_STACK, 0, 0)
+        return (ent, arg, F_STACK, 0, 0, 0)
 
     def x(ent, arg):
-        return (ent, arg, F_AX, 0, 0)
+        return (ent, arg, F_AX, 0, 0, 0)
 
     def n(ent):
-        return (ent, 0, 0, 0, 0)
+        return (ent, 0, 0, 0, 0, 0)
 
     def i(ent):
-        return (ent, 0, F_INL, 0, 0)
+        return (ent, 0, F_INL, 0, 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)
+        return (ent, arg, F_STACK | F_STACK2, arg2, 0, 0)
 
     def s3(ent, a, b, c):
         """Three stack words: c is pushed first (landing at [BP+8]), then b
         ([BP+6]), then a ([BP+4])."""
-        return (ent, a, F_STACK | F_STACK3, b, c)
+        return (ent, a, F_STACK | F_STACK3, b, c, 0)
+
+    def s4(ent, a, b, c, d):
+        """Four stack words, d first: d=[BP+10], c=[BP+8], b=[BP+6],
+        a=[BP+4]."""
+        return (ent, a, F_STACK | F_STACK4, b, c, d)
 
     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
         the only entry that wants all three."""
-        return (ent, arg, F_STACK | F_STACK2 | F_CX, arg2, cx)
+        return (ent, arg, F_STACK | F_STACK2 | F_CX, arg2, cx, 0)
 
     def scx(ent, arg, cx):
         """One stack word and CX, for rdstr: dest at [BP+4], limit in CX."""
-        return (ent, arg, F_STACK | F_CX, 0, cx)
+        return (ent, arg, F_STACK | F_CX, 0, cx, 0)
 
     # initmem is handed the header in AX -- and the header's MEMORY address, not
     # its image offset.  Given the image offset it reads the two words it wants
@@ -688,6 +695,37 @@ def build_cases():
     wr("real_write(3.7:18:FFFF)", 0x1370, 18, 0xFFFF,
        b"  3.7000000000E+00")
 
+    # --- set algebra ----------------------------------------------------
+    # set_op: [BP+4]=(op<<8)|size, [BP+6]=dest, [BP+8]=right, [BP+10]=left.
+    # 0 = intersection, 1 = union, 2 = difference.
+    def setop(name, op, size, dest, right, left, want):
+        add(name, [s4("set_op", (op << 8) | size, dest + LOAD_BIAS,
+                       right + LOAD_BIAS, left + LOAD_BIAS)],
+            dump=size, dumpbytes=want, dumpadr=dest,
+            note="the loops in EmitSetOps, exact bytes")
+    setop("set_op /", 0, 1, 0x15B0, 0x1590, 0x1580, b"\x08")
+    setop("set_op +", 1, 1, 0x15B0, 0x1590, 0x1580, b"\x1A")
+    setop("set_op -", 2, 1, 0x15B0, 0x1590, 0x1580, b"\x02")
+    setop("set_op / 2 bytes", 0, 2, 0x15E0, 0x15D0, 0x15C0, b"\x08\x03")
+
+    # set_cmp: [BP+4]=size, [BP+6]=right, [BP+8]=left; AX = equal bit 0,
+    # strict subset bit 1, strict superset bit 2.
+    def setcmp(name, size, left, right, mask):
+        add(name, [s3("set_cmp", size, right + LOAD_BIAS, left + LOAD_BIAS)],
+            dump=2, dumpbytes=struct.pack("<H", mask), dumpadr=AXOUT,
+            note="the reference mask: 1 equal, 2 subset, 4 superset")
+    setcmp("set_cmp size 0", 0, 0x1580, 0x1590, 1)
+    setcmp("set_cmp A=A", 1, 0x1580, 0x1580, 1)
+    setcmp("set_cmp A~B", 1, 0x1580, 0x1590, 0)
+    setcmp("set_cmp C<A", 1, 0x15A0, 0x1580, 2)
+    setcmp("set_cmp A>C", 1, 0x1580, 0x15A0, 4)
+    setcmp("set_cmp 2-byte incomparable", 2, 0x15C0, 0x15D0, 0)
+
+    add("set_copy 2 bytes",
+        [s3("set_copy", 2, 0x15C0 + LOAD_BIAS, 0x15E0 + LOAD_BIAS)],
+        dump=2, dumpbytes=b"\x0A\x0F", dumpadr=0x15E0,
+        note="[BP+4]=size, [BP+6]=src, [BP+8]=dest")
+
     add("wrchar('A')", [s("wrchar", ord("A"))], out=b"A")
     add("wrchar('!')", [s("wrchar", ord("!"))], out=b"!")
 

+ 200 - 94
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 DC 0D    mov bx,0ddch
+003C  BB 98 0E    mov bx,0e98h
 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 DC 0D    mov bx,0ddch
+00A8  BB 98 0E    mov bx,0e98h
 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 E1 0D    mov dx,0de1h
+00E6  BA 9D 0E    mov dx,0e9dh
 00E9  EB 03       jmp 0eeh
-00EB  BA DC 0D    mov dx,0ddch
+00EB  BA 98 0E    mov dx,0e98h
 00EE  B4 09       mov ah,9h
 00F0  CD 21       int 21h
 00F2  5D          pop bp
 00F3  C3          ret
-00F4  BA EA 0D    mov dx,0deah
+00F4  BA A6 0E    mov dx,0ea6h
 00F7  B4 09       mov ah,9h
 00F9  CD 21       int 21h
 00FB  C3          ret
-00FC  BA E7 0D    mov dx,0de7h
+00FC  BA A3 0E    mov dx,0ea3h
 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 F0 0D mov bx,word ptr [0df0h]
+021B  8B 1E AC 0E mov bx,word ptr [0each]
 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 F0 0D mov word ptr [0df0h],bx
+0228  89 1E AC 0E mov word ptr [0each],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 F0 0D mov word ptr [0df0h],bx
+0239  89 1E AC 0E mov word ptr [0each],bx
 023D  C3          ret
 023E  55          push bp
 023F  8B EC       mov bp,sp
@@ -543,7 +543,7 @@
 03C2  8B 4D 06    mov cx,word ptr [di+6h]
 03C5  8B 75 08    mov si,word ptr [di+8h]
 03C8  8B 7D 0A    mov di,word ptr [di+0ah]
-03CB  C7 06 F2 0D 00 00 mov word ptr [0df2h],0h
+03CB  C7 06 AE 0E 00 00 mov word ptr [0eaeh],0h
 03D1  E8 4D 00    call 421h
 03D4  5F          pop di
 03D5  89 05       mov word ptr [di],ax
@@ -572,7 +572,7 @@
 03FE  8B 4D 06    mov cx,word ptr [di+6h]
 0401  8B 75 08    mov si,word ptr [di+8h]
 0404  8B 7D 0A    mov di,word ptr [di+0ah]
-0407  C7 06 F2 0D 00 80 mov word ptr [0df2h],8000h
+0407  C7 06 AE 0E 00 80 mov word ptr [0eaeh],8000h
 040D  E8 11 00    call 421h
 0410  5F          pop di
 0411  89 05       mov word ptr [di],ax
@@ -587,7 +587,7 @@
 0420  C3          ret
 0421  80 F9 00    cmp cl,0h
 0424  74 0E       je 434h
-0426  33 3E F2 0D xor di,word ptr [0df2h]
+0426  33 3E AE 0E xor di,word ptr [0eaeh]
 042A  3C 00       cmp al,0h
 042C  75 07       jne 435h
 042E  89 C8       mov ax,cx
@@ -599,16 +599,16 @@
 0439  91          xchg ax,cx
 043A  87 F3       xchg bx,si
 043C  87 FA       xchg dx,di
-043E  88 0E F6 0D mov byte ptr [0df6h],cl
+043E  88 0E B2 0E mov byte ptr [0eb2h],cl
 0442  28 C1       sub cl,al
 0444  80 F9 28    cmp cl,28h
 0447  72 06       jb 44fh
-0449  8A 0E F6 0D mov cl,byte ptr [0df6h]
+0449  8A 0E B2 0E mov cl,byte ptr [0eb2h]
 044D  EB DF       jmp 42eh
-044F  89 3E F2 0D mov word ptr [0df2h],di
-0453  80 26 F3 0D 80 and byte ptr [0df3h],80h
-0458  89 3E F4 0D mov word ptr [0df4h],di
-045C  30 36 F5 0D xor byte ptr [0df5h],dh
+044F  89 3E AE 0E mov word ptr [0eaeh],di
+0453  80 26 AF 0E 80 and byte ptr [0eafh],80h
+0458  89 3E B0 0E mov word ptr [0eb0h],di
+045C  30 36 B1 0E xor byte ptr [0eb1h],dh
 0460  81 CF 00 80 or di,8000h
 0464  80 CE 80    or dh,80h
 0467  80 F9 10    cmp cl,10h
@@ -633,8 +633,8 @@
 0492  D0 DC       rcr ah,1h
 0494  FE C9       dec cl
 0496  75 F6       jne 48eh
-0498  A0 F6 0D    mov al,byte ptr [0df6h]
-049B  F6 06 F5 0D 80 test byte ptr [0df5h],80h
+0498  A0 B2 0E    mov al,byte ptr [0eb2h]
+049B  F6 06 B1 0E 80 test byte ptr [0eb1h],80h
 04A0  75 14       jne 4b6h
 04A2  00 EC       add ah,ch
 04A4  11 F3       adc bx,si
@@ -654,7 +654,7 @@
 04BE  19 F3       sbb bx,si
 04C0  19 FA       sbb dx,di
 04C2  73 14       jnb 4d8h
-04C4  80 36 F3 0D 80 xor byte ptr [0df3h],80h
+04C4  80 36 AF 0E 80 xor byte ptr [0eafh],80h
 04C9  F6 D4       not ah
 04CB  F7 D3       not bx
 04CD  F7 D2       not dx
@@ -686,7 +686,7 @@
 0506  31 D2       xor dx,dx
 0508  C3          ret
 0509  80 E6 7F    and dh,7fh
-050C  32 36 F3 0D xor dh,byte ptr [0df3h]
+050C  32 36 AF 0E xor dh,byte ptr [0eafh]
 0510  C3          ret
 0511  55          push bp
 0512  8B EC       mov bp,sp
@@ -732,23 +732,23 @@
 0565  B0 01       mov al,1h
 0567  B4 4C       mov ah,4ch
 0569  CD 21       int 21h
-056B  89 0E FD 0D mov word ptr [0dfdh],cx
+056B  89 0E B9 0E mov word ptr [0eb9h],cx
 056F  8B CA       mov cx,dx
 0571  31 F9       xor cx,di
 0573  F6 D5       not ch
 0575  80 E5 80    and ch,80h
-0578  88 2E F3 0D mov byte ptr [0df3h],ch
+0578  88 2E AF 0E mov byte ptr [0eafh],ch
 057C  80 CE 80    or dh,80h
 057F  81 CF 00 80 or di,8000h
-0583  89 36 FF 0D mov word ptr [0dffh],si
-0587  89 3E 01 0E mov word ptr [0e01h],di
-058B  A3 F7 0D    mov word ptr [0df7h],ax
-058E  89 1E F9 0D mov word ptr [0df9h],bx
-0592  89 16 FB 0D mov word ptr [0dfbh],dx
+0583  89 36 BB 0E mov word ptr [0ebbh],si
+0587  89 3E BD 0E mov word ptr [0ebdh],di
+058B  A3 B3 0E    mov word ptr [0eb3h],ax
+058E  89 1E B5 0E mov word ptr [0eb5h],bx
+0592  89 16 B7 0E mov word ptr [0eb7h],dx
 0596  30 E4       xor ah,ah
 0598  31 DB       xor bx,bx
 059A  31 D2       xor dx,dx
-059C  BF FD 0D    mov di,0dfdh
+059C  BF B9 0E    mov di,0eb9h
 059F  B1 05       mov cl,5h
 05A1  47          inc di
 05A2  8A 2D       mov ch,byte ptr [di]
@@ -763,9 +763,9 @@
 05B5  BE 08 00    mov si,8h
 05B8  D0 DD       rcr ch,1h
 05BA  73 0C       jnb 5c8h
-05BC  02 26 F8 0D add ah,byte ptr [0df8h]
-05C0  13 1E F9 0D adc bx,word ptr [0df9h]
-05C4  13 16 FB 0D adc dx,word ptr [0dfbh]
+05BC  02 26 B4 0E add ah,byte ptr [0eb4h]
+05C0  13 1E B5 0E adc bx,word ptr [0eb5h]
+05C4  13 16 B7 0E adc dx,word ptr [0eb7h]
 05C8  D1 DA       rcr dx,1h
 05CA  D1 DB       rcr bx,1h
 05CC  D0 DC       rcr ah,1h
@@ -785,7 +785,7 @@
 05E6  74 02       je 5eah
 05E8  FE C9       dec cl
 05EA  91          xchg ax,cx
-05EB  32 36 F3 0D xor dh,byte ptr [0df3h]
+05EB  32 36 AF 0E xor dh,byte ptr [0eafh]
 05EF  3C 00       cmp al,0h
 05F1  75 06       jne 5f9h
 05F3  31 C0       xor ax,ax
@@ -834,29 +834,29 @@
 0647  B0 01       mov al,1h
 0649  B4 4C       mov ah,4ch
 064B  CD 21       int 21h
-064D  89 0E FD 0D mov word ptr [0dfdh],cx
+064D  89 0E B9 0E mov word ptr [0eb9h],cx
 0651  8B CA       mov cx,dx
 0653  31 F9       xor cx,di
 0655  F6 D5       not ch
 0657  80 E5 80    and ch,80h
-065A  88 2E F3 0D mov byte ptr [0df3h],ch
+065A  88 2E AF 0E mov byte ptr [0eafh],ch
 065E  80 CE 80    or dh,80h
 0661  81 CF 00 80 or di,8000h
-0665  89 36 FF 0D mov word ptr [0dffh],si
-0669  89 3E 01 0E mov word ptr [0e01h],di
-066D  A2 F7 0D    mov byte ptr [0df7h],al
-0670  BF FC 0D    mov di,0dfch
+0665  89 36 BB 0E mov word ptr [0ebbh],si
+0669  89 3E BD 0E mov word ptr [0ebdh],di
+066D  A2 B3 0E    mov byte ptr [0eb3h],al
+0670  BF B8 0E    mov di,0eb8h
 0673  B1 05       mov cl,5h
 0675  BE 08 00    mov si,8h
-0678  3B 16 01 0E cmp dx,word ptr [0e01h]
+0678  3B 16 BD 0E cmp dx,word ptr [0ebdh]
 067C  75 0A       jne 688h
-067E  3B 1E FF 0D cmp bx,word ptr [0dffh]
+067E  3B 1E BB 0E cmp bx,word ptr [0ebbh]
 0682  75 04       jne 688h
-0684  3A 26 FE 0D cmp ah,byte ptr [0dfeh]
+0684  3A 26 BA 0E cmp ah,byte ptr [0ebah]
 0688  72 0C       jb 696h
-068A  2A 26 FE 0D sub ah,byte ptr [0dfeh]
-068E  1B 1E FF 0D sbb bx,word ptr [0dffh]
-0692  1B 16 01 0E sbb dx,word ptr [0e01h]
+068A  2A 26 BA 0E sub ah,byte ptr [0ebah]
+068E  1B 1E BB 0E sbb bx,word ptr [0ebbh]
+0692  1B 16 BD 0E sbb dx,word ptr [0ebdh]
 0696  F5          cmc
 0697  D0 D5       rcl ch,1h
 0699  4E          dec si
@@ -870,24 +870,24 @@
 06A8  D1 D3       rcl bx,1h
 06AA  D1 D2       rcl dx,1h
 06AC  73 CA       jnb 678h
-06AE  2A 26 FE 0D sub ah,byte ptr [0dfeh]
-06B2  1B 1E FF 0D sbb bx,word ptr [0dffh]
-06B6  1B 16 01 0E sbb dx,word ptr [0e01h]
+06AE  2A 26 BA 0E sub ah,byte ptr [0ebah]
+06B2  1B 1E BB 0E sbb bx,word ptr [0ebbh]
+06B6  1B 16 BD 0E sbb dx,word ptr [0ebdh]
 06BA  F8          clc
 06BB  EB D9       jmp 696h
 06BD  D0 E4       shl ah,1h
 06BF  D1 D3       rcl bx,1h
 06C1  D1 D2       rcl dx,1h
 06C3  72 11       jb 6d6h
-06C5  3B 16 01 0E cmp dx,word ptr [0e01h]
+06C5  3B 16 BD 0E cmp dx,word ptr [0ebdh]
 06C9  75 0A       jne 6d5h
-06CB  3B 1E FF 0D cmp bx,word ptr [0dffh]
+06CB  3B 1E BB 0E cmp bx,word ptr [0ebbh]
 06CF  75 04       jne 6d5h
-06D1  3A 26 FE 0D cmp ah,byte ptr [0dfeh]
+06D1  3A 26 BA 0E cmp ah,byte ptr [0ebah]
 06D5  F5          cmc
-06D6  8B 0E F7 0D mov cx,word ptr [0df7h]
-06DA  8B 1E F9 0D mov bx,word ptr [0df9h]
-06DE  8B 16 FB 0D mov dx,word ptr [0dfbh]
+06D6  8B 0E B3 0E mov cx,word ptr [0eb3h]
+06DA  8B 1E B5 0E mov bx,word ptr [0eb5h]
+06DE  8B 16 B7 0E mov dx,word ptr [0eb7h]
 06E2  9F          lahf
 06E3  F6 C6 80    test dh,80h
 06E6  75 09       jne 6f1h
@@ -1030,47 +1030,47 @@
 07EC  B5 00       mov ch,0h
 07EE  B6 00       mov dh,0h
 07F0  51          push cx
-07F1  BB 4D 0E    mov bx,0e4dh
+07F1  BB 09 0F    mov bx,0f09h
 07F4  E8 41 02    call 0a38h
 07F7  59          pop cx
 07F8  89 D8       mov ax,bx
-07FA  BB 4D 0E    mov bx,0e4dh
+07FA  BB 09 0F    mov bx,0f09h
 07FD  2B C3       sub ax,bx
-07FF  A3 8D 0E    mov word ptr [0e8dh],ax
-0802  89 0E 8F 0E mov word ptr [0e8fh],cx
-0806  8B 06 8F 0E mov ax,word ptr [0e8fh]
-080A  8B 16 8D 0E mov dx,word ptr [0e8dh]
+07FF  A3 49 0F    mov word ptr [0f49h],ax
+0802  89 0E 4B 0F mov word ptr [0f4bh],cx
+0806  8B 06 4B 0F mov ax,word ptr [0f4bh]
+080A  8B 16 49 0F mov dx,word ptr [0f49h]
 080E  2B C2       sub ax,dx
 0810  83 F8 00    cmp ax,0h
 0813  7D 02       jnl 817h
 0815  31 C0       xor ax,ax
-0817  A3 91 0E    mov word ptr [0e91h],ax
-081A  8B 16 8D 0E mov dx,word ptr [0e8dh]
+0817  A3 4D 0F    mov word ptr [0f4dh],ax
+081A  8B 16 49 0F mov dx,word ptr [0f49h]
 081E  03 C2       add ax,dx
 0820  3D FF 00    cmp ax,0ffh
 0823  77 03       jnbe 828h
 0825  E9 03 00    jmp 82bh
 0828  B8 FF 00    mov ax,0ffh
-082B  A3 93 0E    mov word ptr [0e93h],ax
+082B  A3 4F 0F    mov word ptr [0f4fh],ax
 082E  8B 56 06    mov dx,word ptr [bp+6h]
 0831  89 D7       mov di,dx
 0833  88 05       mov byte ptr [di],al
 0835  47          inc di
-0836  8B 0E 91 0E mov cx,word ptr [0e91h]
+0836  8B 0E 4D 0F mov cx,word ptr [0f4dh]
 083A  E3 07       jcxz 843h
 083C  B0 20       mov al,20h
 083E  88 05       mov byte ptr [di],al
 0840  47          inc di
 0841  E2 F9       loop 83ch
-0843  BE 4D 0E    mov si,0e4dh
-0846  8B 0E 8D 0E mov cx,word ptr [0e8dh]
+0843  BE 09 0F    mov si,0f09h
+0846  8B 0E 49 0F mov cx,word ptr [0f49h]
 084A  E3 08       jcxz 854h
 084C  8A 04       mov al,byte ptr [si]
 084E  88 05       mov byte ptr [di],al
 0850  46          inc si
 0851  47          inc di
 0852  E2 F8       loop 84ch
-0854  8B 06 93 0E mov ax,word ptr [0e93h]
+0854  8B 06 4F 0F mov ax,word ptr [0f4fh]
 0858  5F          pop di
 0859  5E          pop si
 085A  5A          pop dx
@@ -1086,12 +1086,12 @@
 0866  57          push di
 0867  8B 5E 04    mov bx,word ptr [bp+4h]
 086A  8B 7E 06    mov di,word ptr [bp+6h]
-086D  89 1E 95 0E mov word ptr [0e95h],bx
+086D  89 1E 51 0F mov word ptr [0f51h],bx
 0871  E8 CF 02    call 0b43h
 0874  73 03       jnb 879h
 0876  E9 0B 00    jmp 884h
 0879  89 D8       mov ax,bx
-087B  8B 16 95 0E mov dx,word ptr [0e95h]
+087B  8B 16 51 0F mov dx,word ptr [0f51h]
 087F  2B C2       sub ax,dx
 0881  E9 03 00    jmp 887h
 0884  B8 FF FF    mov ax,0ffffh
@@ -1140,7 +1140,7 @@
 08CE  52          push dx
 08CF  53          push bx
 08D0  50          push ax
-08D1  88 0E 0F 0E mov byte ptr [0e0fh],cl
+08D1  88 0E CB 0E mov byte ptr [0ecbh],cl
 08D5  80 F9 00    cmp cl,0h
 08D8  78 03       js 8ddh
 08DA  E9 02 00    jmp 8dfh
@@ -1151,7 +1151,7 @@
 08E6  D0 EB       shr bl,1h
 08E8  00 FB       add bl,bh
 08EA  30 FF       xor bh,bh
-08EC  8D BF 11 0E lea di,[bx+0e11h]
+08EC  8D BF CD 0E lea di,[bx+0ecdh]
 08F0  8B 05       mov ax,word ptr [di]
 08F2  8B 5D 02    mov bx,word ptr [di+2h]
 08F5  8B 55 04    mov dx,word ptr [di+4h]
@@ -1168,7 +1168,7 @@
 0910  58          pop ax
 0911  5B          pop bx
 0912  5A          pop dx
-0913  F6 06 0F 0E 80 test byte ptr [0e0fh],80h
+0913  F6 06 CB 0E 80 test byte ptr [0ecbh],80h
 0918  74 03       je 91dh
 091A  E9 03 00    jmp 920h
 091D  E9 27 FC    jmp 547h
@@ -1195,11 +1195,11 @@
 0947  3C 00       cmp al,0h
 0949  74 03       je 94eh
 094B  E9 16 00    jmp 964h
-094E  BE 03 0E    mov si,0e03h
+094E  BE BF 0E    mov si,0ebfh
 0951  C7 04 30 30 mov word ptr [si],3030h
 0955  46          inc si
 0956  46          inc si
-0957  81 FE 0F 0E cmp si,0e0fh
+0957  81 FE CB 0E cmp si,0ecbh
 095B  74 03       je 960h
 095D  E9 F1 FF    jmp 951h
 0960  B9 00 00    mov cx,0h
@@ -1243,7 +1243,7 @@
 09AF  FE C9       dec cl
 09B1  74 03       je 9b6h
 09B3  E9 F3 FF    jmp 9a9h
-09B6  BE 03 0E    mov si,0e03h
+09B6  BE BF 0E    mov si,0ebfh
 09B9  88 F5       mov ch,dh
 09BB  B1 04       mov cl,4h
 09BD  D2 ED       shr ch,cl
@@ -1269,13 +1269,13 @@
 09E1  D1 D3       rcl bx,1h
 09E3  D1 D2       rcl dx,1h
 09E5  46          inc si
-09E6  81 FE 0F 0E cmp si,0e0fh
+09E6  81 FE CB 0E cmp si,0ecbh
 09EA  74 03       je 9efh
 09EC  E9 CA FF    jmp 9b9h
 09EF  59          pop cx
 09F0  C3          ret
 09F1  30 E4       xor ah,ah
-09F3  BB 03 0E    mov bx,0e03h
+09F3  BB BF 0E    mov bx,0ebfh
 09F6  03 D8       add bx,ax
 09F8  80 3F 35    cmp byte ptr [bx],35h
 09FB  C6 07 00    mov byte ptr [bx],0h
@@ -1346,7 +1346,7 @@
 0A92  00 C8       add al,cl
 0A94  78 03       js 0a99h
 0A96  E9 08 00    jmp 0aa1h
-0A99  C6 06 03 0E 00 mov byte ptr [0e03h],0h
+0A99  C6 06 BF 0E 00 mov byte ptr [0ebfh],0h
 0A9E  E9 0C 00    jmp 0aadh
 0AA1  3C 0C       cmp al,0ch
 0AA3  73 03       jnb 0aa8h
@@ -1354,7 +1354,7 @@
 0AA8  B0 0B       mov al,0bh
 0AAA  E8 44 FF    call 9f1h
 0AAD  5B          pop bx
-0AAE  BE 03 0E    mov si,0e03h
+0AAE  BE BF 0E    mov si,0ebfh
 0AB1  F6 C5 80    test ch,80h
 0AB4  75 03       jne 0ab9h
 0AB6  E9 05 00    jmp 0abeh
@@ -1440,7 +1440,7 @@
 0B75  31 DB       xor bx,bx
 0B77  31 D2       xor dx,dx
 0B79  31 C9       xor cx,cx
-0B7B  C6 06 10 0E 00 mov byte ptr [0e10h],0h
+0B7B  C6 06 CC 0E 00 mov byte ptr [0ecch],0h
 0B80  8A 0C       mov cl,byte ptr [si]
 0B82  80 F9 61    cmp cl,61h
 0B85  73 03       jnb 0b8ah
@@ -1469,7 +1469,7 @@
 0BB5  58          pop ax
 0BB6  5B          pop bx
 0BB7  5A          pop dx
-0BB8  C7 06 F2 0D 00 00 mov word ptr [0df2h],0h
+0BB8  C7 06 AE 0E 00 00 mov word ptr [0eaeh],0h
 0BBE  E8 60 F8    call 421h
 0BC1  59          pop cx
 0BC2  5E          pop si
@@ -1477,7 +1477,7 @@
 0BC4  F6 C5 40    test ch,40h
 0BC7  75 03       jne 0bcch
 0BC9  E9 04 00    jmp 0bd0h
-0BCC  FE 0E 10 0E dec byte ptr [0e10h]
+0BCC  FE 0E CC 0E dec byte ptr [0ecch]
 0BD0  46          inc si
 0BD1  E9 AC FF    jmp 0b80h
 0BD4  80 F9 2E    cmp cl,2eh
@@ -1489,7 +1489,7 @@
 0BE4  80 CD 40    or ch,40h
 0BE7  E9 E6 FF    jmp 0bd0h
 0BEA  80 F9 45    cmp cl,45h
-0BED  8A 0E 10 0E mov cl,byte ptr [0e10h]
+0BED  8A 0E CC 0E mov cl,byte ptr [0ecch]
 0BF1  74 03       je 0bf6h
 0BF3  E9 4D 00    jmp 0c43h
 0BF6  E8 2A FD    call 923h
@@ -1555,31 +1555,31 @@
 0C77  56          push si
 0C78  57          push di
 0C79  8B 46 06    mov ax,word ptr [bp+6h]
-0C7C  A3 8F 0E    mov word ptr [0e8fh],ax
+0C7C  A3 4B 0F    mov word ptr [0f4bh],ax
 0C7F  8B 56 04    mov dx,word ptr [bp+4h]
 0C82  8B 4E 06    mov cx,word ptr [bp+6h]
 0C85  8B 7E 08    mov di,word ptr [bp+8h]
-0C88  BB 4D 0E    mov bx,0e4dh
+0C88  BB 09 0F    mov bx,0f09h
 0C8B  E8 AA FD    call 0a38h
 0C8E  89 D8       mov ax,bx
-0C90  BB 4D 0E    mov bx,0e4dh
+0C90  BB 09 0F    mov bx,0f09h
 0C93  2B C3       sub ax,bx
-0C95  A3 8D 0E    mov word ptr [0e8dh],ax
-0C98  8B 06 8F 0E mov ax,word ptr [0e8fh]
-0C9C  8B 16 8D 0E mov dx,word ptr [0e8dh]
+0C95  A3 49 0F    mov word ptr [0f49h],ax
+0C98  8B 06 4B 0F mov ax,word ptr [0f4bh]
+0C9C  8B 16 49 0F mov dx,word ptr [0f49h]
 0CA0  2B C2       sub ax,dx
 0CA2  83 F8 00    cmp ax,0h
 0CA5  7D 02       jnl 0ca9h
 0CA7  31 C0       xor ax,ax
-0CA9  A3 91 0E    mov word ptr [0e91h],ax
-0CAC  8B 0E 91 0E mov cx,word ptr [0e91h]
+0CA9  A3 4D 0F    mov word ptr [0f4dh],ax
+0CAC  8B 0E 4D 0F mov cx,word ptr [0f4dh]
 0CB0  E3 08       jcxz 0cbah
 0CB2  B0 20       mov al,20h
 0CB4  B4 02       mov ah,2h
 0CB6  CD 21       int 21h
 0CB8  E2 F8       loop 0cb2h
-0CBA  BE 4D 0E    mov si,0e4dh
-0CBD  8B 0E 8D 0E mov cx,word ptr [0e8dh]
+0CBA  BE 09 0F    mov si,0f09h
+0CBD  8B 0E 49 0F mov cx,word ptr [0f49h]
 0CC1  E3 09       jcxz 0ccch
 0CC3  8A 04       mov al,byte ptr [si]
 0CC5  B4 02       mov ah,2h
@@ -1593,3 +1593,109 @@
 0CD0  89 EC       mov sp,bp
 0CD2  5D          pop bp
 0CD3  C3          ret
+0CD4  55          push bp
+0CD5  8B EC       mov bp,sp
+0CD7  53          push bx
+0CD8  51          push cx
+0CD9  52          push dx
+0CDA  56          push si
+0CDB  57          push di
+0CDC  8B 46 04    mov ax,word ptr [bp+4h]
+0CDF  88 C6       mov dh,al
+0CE1  8B 76 0A    mov si,word ptr [bp+0ah]
+0CE4  8B 7E 08    mov di,word ptr [bp+8h]
+0CE7  8B 5E 06    mov bx,word ptr [bp+6h]
+0CEA  80 FE 00    cmp dh,0h
+0CED  74 23       je 0d12h
+0CEF  8A 04       mov al,byte ptr [si]
+0CF1  8A 15       mov dl,byte ptr [di]
+0CF3  80 FC 00    cmp ah,0h
+0CF6  74 0B       je 0d03h
+0CF8  80 FC 01    cmp ah,1h
+0CFB  74 0A       je 0d07h
+0CFD  F6 D2       not dl
+0CFF  20 D0       and al,dl
+0D01  EB 06       jmp 0d09h
+0D03  20 D0       and al,dl
+0D05  EB 02       jmp 0d09h
+0D07  08 D0       or al,dl
+0D09  88 07       mov byte ptr [bx],al
+0D0B  46          inc si
+0D0C  47          inc di
+0D0D  43          inc bx
+0D0E  FE CE       dec dh
+0D10  75 DD       jne 0cefh
+0D12  5F          pop di
+0D13  5E          pop si
+0D14  5A          pop dx
+0D15  59          pop cx
+0D16  5B          pop bx
+0D17  89 EC       mov sp,bp
+0D19  5D          pop bp
+0D1A  C3          ret
+0D1B  55          push bp
+0D1C  8B EC       mov bp,sp
+0D1E  53          push bx
+0D1F  51          push cx
+0D20  52          push dx
+0D21  56          push si
+0D22  57          push di
+0D23  8B 4E 04    mov cx,word ptr [bp+4h]
+0D26  8B 76 08    mov si,word ptr [bp+8h]
+0D29  8B 7E 06    mov di,word ptr [bp+6h]
+0D2C  B4 07       mov ah,7h
+0D2E  E3 2B       jcxz 0d5bh
+0D30  8A 04       mov al,byte ptr [si]
+0D32  8A 15       mov dl,byte ptr [di]
+0D34  88 C3       mov bl,al
+0D36  88 D6       mov dh,dl
+0D38  3A C2       cmp al,dl
+0D3A  74 03       je 0d3fh
+0D3C  80 E4 FE    and ah,0feh
+0D3F  F6 D2       not dl
+0D41  20 D0       and al,dl
+0D43  3C 00       cmp al,0h
+0D45  74 03       je 0d4ah
+0D47  80 E4 FD    and ah,0fdh
+0D4A  8A C3       mov al,bl
+0D4C  F6 D0       not al
+0D4E  20 F0       and al,dh
+0D50  3C 00       cmp al,0h
+0D52  74 03       je 0d57h
+0D54  80 E4 FB    and ah,0fbh
+0D57  46          inc si
+0D58  47          inc di
+0D59  E2 D5       loop 0d30h
+0D5B  F6 C4 01    test ah,1h
+0D5E  74 03       je 0d63h
+0D60  80 E4 F9    and ah,0f9h
+0D63  88 E0       mov al,ah
+0D65  B4 00       mov ah,0h
+0D67  5F          pop di
+0D68  5E          pop si
+0D69  5A          pop dx
+0D6A  59          pop cx
+0D6B  5B          pop bx
+0D6C  89 EC       mov sp,bp
+0D6E  5D          pop bp
+0D6F  C3          ret
+0D70  55          push bp
+0D71  8B EC       mov bp,sp
+0D73  53          push bx
+0D74  51          push cx
+0D75  56          push si
+0D76  8B 4E 04    mov cx,word ptr [bp+4h]
+0D79  8B 76 06    mov si,word ptr [bp+6h]
+0D7C  8B 5E 08    mov bx,word ptr [bp+8h]
+0D7F  E3 08       jcxz 0d89h
+0D81  8A 04       mov al,byte ptr [si]
+0D83  88 07       mov byte ptr [bx],al
+0D85  46          inc si
+0D86  43          inc bx
+0D87  E2 F8       loop 0d81h
+0D89  5E          pop si
+0D8A  59          pop cx
+0D8B  5B          pop bx
+0D8C  89 EC       mov sp,bp
+0D8E  5D          pop bp
+0D8F  C3          ret