diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index 6d07a2f0..a3aa3a51 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -373,9 +373,9 @@ Section numbers match the v3 primitive reference. | `0=` `0<` | CAP | §4 | | `0<>` | CAP | `0= 0=` | | `0>` | CAP | `dup 0< SWAP 0= OR 0=` (correct for the most negative number) | -| `=` | CAP | `xor 0=` | +| `=` | CAP | `xor 0=` — executed on the golden model (2026-10-02). | | `<>` | CAP | `xor 0<>` | -| `<` | CAP | `2DUP xor 0< IF drop 0< ELSE - 0< THEN` (overflow-safe) | +| `<` | CAP | `2DUP xor 0< IF drop 0< ELSE - 0< THEN` (overflow-safe) — executed on the golden model (2026-10-02). | | `>` | CAP | `SWAP <` | | `<=` | CAP | `> 0=` | | `>=` | CAP | `< 0=` | @@ -412,15 +412,16 @@ A double is two 32-bit cells on a mesh node. | `D0=` | CAP | `OR 0=` — executed on the golden model (2026-10-02). | | `D0<` | CAP | `NIP 0<` | | `D=` | CAP | `D- D0=` — executed on the golden model (2026-10-02). | -| `D<` | CAP | `ROT 2DUP = IF 2DROP U< ELSE SWAP < NIP NIP THEN` | -| `DMAX` | CAP | `2OVER 2OVER D< IF 2SWAP THEN 2DROP` | -| `DMIN` | CAP | `2OVER 2OVER D< 0= IF 2SWAP THEN 2DROP` | +| `D<` | CAP | `ROT 2DUP = IF 2DROP U< ELSE SWAP < NIP NIP THEN` — executed on the golden model (2026-10-02). | +| `(D<)` | CAP | `( d1 d2 -- d1 d2 flag )`, call-free; internal to `DMAX` and `DMIN`. Copies of the high cells go on top; if they differ the flag comes from them, else from the low cells unsigned. The value tested always has its top bit set exactly when `d1 < d2`: `ah` (high signs differ), `ah - bh` (agree), `bl` (low top bits differ), `al - bl` (agree); `x - y` with `y` on top is `push inv pop + inv`. `dup push push over pop over over xor if TIE drop over over xor -if HS drop drop jump S1 HS: drop push inv pop + inv S1: -if N1 drop -1 jump D1 N1: drop 0 D1: pop SWAP ; TIE: drop drop drop dup push push over pop over over xor -if LS drop NIP jump S2 LS: drop push inv pop + inv S2: -if N2 drop -1 jump D2 N2: drop 0 D2: pop pop ROT ;` with `SWAP`, `NIP` and `ROT` in line. Executed on the golden model (2026-10-02). | +| `DMAX` | CAP | `(D<) if L drop push push drop drop pop pop ; L: drop drop drop ;` Executed on the golden model (2026-10-02). Replaces `2OVER 2OVER D< IF 2SWAP THEN 2DROP`, which needs 8 data cells plus `D<`'s 2: the whole 10-deep data stack, so it failed whenever the caller held anything at all (D-2). | +| `DMIN` | CAP | `(D<) if L drop drop drop ; L: drop push push drop drop pop pop ;` Executed on the golden model (2026-10-02); replaces `2OVER 2OVER D< 0= IF 2SWAP THEN 2DROP` for the same reason as `DMAX`. | | `D2*` | CAP | `2* over 0< NEGATE OR SWAP 2* SWAP` | | `D2/` | CAP | `dup 1 and push 2/ SWAP 1 RSHIFT pop IF MSB OR THEN SWAP` | | `2DROP` | IN | `drop drop` | | `2DUP` | IN | `over over` | -| `2SWAP` | CAP | `ROT push ROT pop` | -| `2OVER` | CAP | `push push 2DUP pop pop 2SWAP` | +| `2SWAP` | CAP | `ROT push ROT pop`, with `ROT` and `SWAP` in line so it makes no calls. Executed on the golden model (2026-10-02). Called, `ROT` and `SWAP` left its caller 2 return entries; in line, 4. | +| `2OVER` | CAP | `push push 2DUP pop pop 2SWAP` — executed on the golden model (2026-10-02). | | `2ROT` | CAP | `2>R 2SWAP 2R> 2SWAP` | | `2>R` | IN | `SWAP push push` | | `2R>` | IN | `pop pop SWAP` | @@ -653,7 +654,7 @@ word. Per D-10 it occupies two cells on a 64-bit node too. Per D-8, v4 Q values | `Q.FROM-INT` | CAP | `push 0 a! 0 pop 15 FOR +* UNEXT push drop a pop` — `n * 2^16` as a signed double. `+*` with `S = 0` never adds, so each step is an exact arithmetic right shift of `T:A`; starting from `T:A = n:0` (that is, `n * 2^N`) and shifting `N-16` bits leaves `n * 2^16`. The count is `N-17`: 15 at 32-bit cells, 47 at 64. Negative values are no longer clamped to 0. Executed on the golden model (2026-10-02): matches v3's `q48_from_u64` for `n >= 0` (at 64-bit cells in the low cell, where v3 wraps for `n >= 2^47`, per D-10). Clobbers `A`. | | `Q.TO-INT` | CAP | `push a! 0 pop 15 FOR +* UNEXT drop drop a` — the same `+*` shift, 16 bits at every cell width, leaving the low cell in `A`. Rounds toward minus infinity, as v3's arithmetic shift does (−1.5 gives −2). Executed on the golden model (2026-10-02): v3's `q48_to_u64` exactly at 64-bit cells, its low cell at 32. Clobbers `A`. | | `Q.1` `Q.0` `Q.SCALE` | IN | Double-cell constants. | -| `Q.=` `Q.<` `Q.>` `Q.0=` `Q.MAX` `Q.MIN` | CAP | `D=`, `D<`, `SWAP D<` (for `Q.>`), `D0=`, `DMAX`, `DMIN`. Signed. | +| `Q.=` `Q.<` `Q.>` `Q.0=` `Q.MAX` `Q.MIN` | CAP | `D=`, `D<`, `2SWAP D<` (for `Q.>`), `D0=`, `DMAX`, `DMIN`. Signed (D-8); v3 compared unsigned, so v3 agrees only when both values have the same sign. Executed on the golden model (2026-10-02). `Q.>` was given here as `SWAP D<`, which swaps single cells, not Q values, and gave a wrong answer in 11702 of 20169 test cases. | | `Q.PRINT` | CAP | Pictured output, five fractional digits. | ### 5.27 Inference engine diff --git a/v4/tests/test_foundation.c b/v4/tests/test_foundation.c index da962186..239e9b06 100644 --- a/v4/tests/test_foundation.c +++ b/v4/tests/test_foundation.c @@ -19,7 +19,10 @@ * DABS and DNEGATE words) are checked against v3's 64-bit q48_add, q48_sub, * q48_abs and 0 - q, with Q values two cells wide at both widths (D-10). * Q.FROM-INT and Q.TO-INT are checked against n * 2^16 and q >> 16 in C and - * against v3's q48_from_u64 and q48_to_u64 where v4 keeps v3's meaning. Every word is + * against v3's q48_from_u64 and q48_to_u64 where v4 keeps v3's meaning. + * <, =, D<, 2OVER, the call-free (D<), DMAX, DMIN and 2SWAP, and the Q + * comparisons on them, are checked against C, signed (D-8), and against v3 + * where v3's unsigned Q comparisons agree. Every word is * also probed for how much of the 10- and 9-deep circular stacks (D-2) it * leaves to its caller. * @@ -49,7 +52,8 @@ static v4_cell w_nip, w_swap, w_or, w_negate, w_rot, w_zless, w_zequal, w_2dup, w_minus, w_uless, w_umstar, w_ummod, w_ugreater, w_abs, w_s2d, w_dplus, w_dnegate, w_dabs, w_smrem, w_slashmod, w_mplus, w_dminus, w_d0equal, w_dequal, - w_qfromint, w_qtoint; + w_qfromint, w_qtoint, w_less, w_equal, w_dless, w_2swap, w_2over, + w_dmax, w_dmin, w_qgt_doc, w_qgt, w_dltkeep; #define O(name) v4_asm_op(&as, V4_OP_##name) #define LIT(v) v4_asm_lit(&as, (v4_cell)(v)) @@ -374,6 +378,144 @@ static void build(void) O(MUL_STEP); O(UNEXT); O(DROP); O(DROP); O(PUSH_A); O(SEMI); + /* : = xor 0= ; 5.5 */ + w_equal = v4_asm_label(&as); + O(XOR); CALL(w_zequal); O(SEMI); + + /* : < 2DUP xor 0< IF drop 0< ELSE - 0< THEN ; 5.5 */ + w_less = v4_asm_label(&as); + O(OVER); O(OVER); O(XOR); CALL(w_zless); + ref = v4_asm_branch_fwd(&as, V4_OP_IF); + O(DROP); O(DROP); CALL(w_zless); + { + v4_asm_ref j = v4_asm_branch_fwd(&as, V4_OP_JUMP); + v4_asm_resolve(&as, ref, v4_asm_label(&as)); + O(DROP); CALL(w_minus); CALL(w_zless); + v4_asm_resolve(&as, j, v4_asm_label(&as)); + } + O(SEMI); + + /* : D< ROT 2DUP = IF 2DROP U< ELSE SWAP < NIP NIP THEN ; 5.7 */ + w_dless = v4_asm_label(&as); + CALL(w_rot); O(OVER); O(OVER); CALL(w_equal); + ref = v4_asm_branch_fwd(&as, V4_OP_IF); + O(DROP); O(DROP); O(DROP); CALL(w_uless); + { + v4_asm_ref j = v4_asm_branch_fwd(&as, V4_OP_JUMP); + v4_asm_resolve(&as, ref, v4_asm_label(&as)); + O(DROP); CALL(w_swap); CALL(w_less); CALL(w_nip); CALL(w_nip); + v4_asm_resolve(&as, j, v4_asm_label(&as)); + } + O(SEMI); + + /* : 2SWAP ROT push ROT pop ; 5.7, call-free + * with ROT (push SWAP pop SWAP) and SWAP (over push push drop pop pop) + * written in line: + * push over push push drop pop pop pop over push push drop pop pop + * push + * push over push push drop pop pop pop over push push drop pop pop + * pop ; */ +#define ROT_INLINE() do { O(PUSH); O(OVER); O(PUSH); O(PUSH); O(DROP); O(RPOP); O(RPOP); \ + O(RPOP); O(OVER); O(PUSH); O(PUSH); O(DROP); O(RPOP); O(RPOP); } while (0) + w_2swap = v4_asm_label(&as); + ROT_INLINE(); O(PUSH); ROT_INLINE(); O(RPOP); O(SEMI); +#undef ROT_INLINE + + /* : 2OVER push push 2DUP pop pop 2SWAP ; 5.7 */ + w_2over = v4_asm_label(&as); + O(PUSH); O(PUSH); O(OVER); O(OVER); O(RPOP); O(RPOP); CALL(w_2swap); O(SEMI); + + /* : (D<) ( d1 d2 -- d1 d2 flag ) 5.7, call-free + * Signed d1 < d2, leaving both doubles. Copies of ah and bh go on top; + * if they differ, flag comes from them, else from al U< bl. In every + * case the tested value has its top bit set exactly when d1 < d2: + * highs, signs differ: ah (d1 < d2 iff ah < 0) + * highs, signs agree: ah - bh (cannot overflow) + * lows, top bits differ: bl (al U< bl iff bl's is set) + * lows, top bits agree: al - bl + * x - y with y on top is `push inv pop + inv`. + * + * dup push push over pop al ah bl ah bh R: bh + * over over xor if TIE + * drop over over xor -if HS + * drop drop jump S1 al ah bl ah + * HS: drop push inv pop + inv al ah bl ah-bh + * S1: -if N1 drop -1 jump D1 N1: drop 0 + * D1: pop over push push drop pop pop ; al ah bl bh f + * TIE: drop drop drop al ah bl R: bh + * dup push push over pop al ah al bl R: bh bl + * over over xor -if LS + * drop push drop pop jump S2 al ah bl + * LS: drop push inv pop + inv al ah al-bl + * S2: -if N2 drop -1 jump D2 N2: drop 0 + * D2: pop pop al ah f bl bh + * push over push push drop pop pop pop over push push drop pop pop ; */ + { + v4_asm_ref tie, hs, s1a, n1, d1, ls, s2a, n2, d2; + w_dltkeep = v4_asm_label(&as); + O(DUP); O(PUSH); O(PUSH); O(OVER); O(RPOP); + O(OVER); O(OVER); O(XOR); + tie = v4_asm_branch_fwd(&as, V4_OP_IF); + O(DROP); O(OVER); O(OVER); O(XOR); + hs = v4_asm_branch_fwd(&as, V4_OP_MINUS_IF); + O(DROP); O(DROP); + s1a = v4_asm_branch_fwd(&as, V4_OP_JUMP); + v4_asm_resolve(&as, hs, v4_asm_label(&as)); + O(DROP); O(PUSH); O(INV); O(RPOP); O(ADD); O(INV); + v4_asm_resolve(&as, s1a, v4_asm_label(&as)); + n1 = v4_asm_branch_fwd(&as, V4_OP_MINUS_IF); + O(DROP); LIT(-1); + d1 = v4_asm_branch_fwd(&as, V4_OP_JUMP); + v4_asm_resolve(&as, n1, v4_asm_label(&as)); + O(DROP); LIT(0); + v4_asm_resolve(&as, d1, v4_asm_label(&as)); + O(RPOP); O(OVER); O(PUSH); O(PUSH); O(DROP); O(RPOP); O(RPOP); O(SEMI); + + v4_asm_resolve(&as, tie, v4_asm_label(&as)); + O(DROP); O(DROP); O(DROP); + O(DUP); O(PUSH); O(PUSH); O(OVER); O(RPOP); + O(OVER); O(OVER); O(XOR); + ls = v4_asm_branch_fwd(&as, V4_OP_MINUS_IF); + O(DROP); O(PUSH); O(DROP); O(RPOP); + s2a = v4_asm_branch_fwd(&as, V4_OP_JUMP); + v4_asm_resolve(&as, ls, v4_asm_label(&as)); + O(DROP); O(PUSH); O(INV); O(RPOP); O(ADD); O(INV); + v4_asm_resolve(&as, s2a, v4_asm_label(&as)); + n2 = v4_asm_branch_fwd(&as, V4_OP_MINUS_IF); + O(DROP); LIT(-1); + d2 = v4_asm_branch_fwd(&as, V4_OP_JUMP); + v4_asm_resolve(&as, n2, v4_asm_label(&as)); + O(DROP); LIT(0); + v4_asm_resolve(&as, d2, v4_asm_label(&as)); + O(RPOP); O(RPOP); + O(PUSH); O(OVER); O(PUSH); O(PUSH); O(DROP); O(RPOP); O(RPOP); + O(RPOP); O(OVER); O(PUSH); O(PUSH); O(DROP); O(RPOP); O(RPOP); O(SEMI); + } + + /* : DMAX ( d1 d2 -- d3 ) (D<) if L drop push push drop drop pop pop ; + * L: drop drop drop ; 5.7 */ + w_dmax = v4_asm_label(&as); + CALL(w_dltkeep); + ref = v4_asm_branch_fwd(&as, V4_OP_IF); + O(DROP); O(PUSH); O(PUSH); O(DROP); O(DROP); O(RPOP); O(RPOP); O(SEMI); + v4_asm_resolve(&as, ref, v4_asm_label(&as)); + O(DROP); O(DROP); O(DROP); O(SEMI); + + /* : DMIN ( d1 d2 -- d3 ) (D<) if L drop drop drop ; + * L: drop push push drop drop pop pop ; 5.7 */ + w_dmin = v4_asm_label(&as); + CALL(w_dltkeep); + ref = v4_asm_branch_fwd(&as, V4_OP_IF); + O(DROP); O(DROP); O(DROP); O(SEMI); + v4_asm_resolve(&as, ref, v4_asm_label(&as)); + O(DROP); O(PUSH); O(PUSH); O(DROP); O(DROP); O(RPOP); O(RPOP); O(SEMI); + + /* Q.> as 5.26 gives it, SWAP D<, and with 2SWAP. */ + w_qgt_doc = v4_asm_label(&as); + CALL(w_swap); CALL(w_dless); O(SEMI); + w_qgt = v4_asm_label(&as); + CALL(w_2swap); CALL(w_dless); O(SEMI); + CHECK(v4_asm_ok(&as), "foundation words assemble"); } @@ -564,6 +706,27 @@ static uint64_t asr64(uint64_t x, unsigned k) return r; } +/* The k results, given bottom first, then the canary under them. */ +static int leftk(const v4_cell *want, unsigned k) +{ + while (k-- > 0) + if (v4_dstack_pop(&n.ds) != want[k]) return 0; + return v4_dstack_pop(&n.ds) == CANARY; +} + +/* Signed double a < b, in C. */ +static int dlt(v4_ucell al, v4_ucell ah, v4_ucell bl, v4_ucell bh) +{ + if (ah != bh) return (v4_cell)ah < (v4_cell)bh; + return al < bl; +} + +/* Q48.16 as v4 reads it (D-8: signed). */ +static int64_t qs(uint64_t q) +{ + return (q >> 63) ? -(int64_t)(~q) - 1 : (int64_t)q; +} + /* Stack headroom. Run `word` with `dfill` marked cells under the canary and * `rfill` marked cells under its return address, and report whether the * results, the canary and every marked cell come back intact. The F18 stacks @@ -575,7 +738,7 @@ static uint64_t asr64(uint64_t x, unsigned k) static int fits(v4_cell word, unsigned argc, const v4_cell *arg, unsigned nres, unsigned dfill, unsigned rfill) { - v4_cell want[4]; + v4_cell want[V4_DATA_DEPTH]; unsigned i; v4_dstack_reset(&n.ds); @@ -583,6 +746,7 @@ static int fits(v4_cell word, unsigned argc, const v4_cell *arg, v4_exec_reset(&es); for (i = 0; i < argc; i++) v4_dstack_push(&n.ds, arg[i]); if (v4_test_call(&n, &es, &h, word, 100000) <= 0) return 0; + if (nres > V4_DATA_DEPTH) return 0; for (i = 0; i < nres; i++) want[i] = v4_dstack_pop(&n.ds); v4_dstack_reset(&n.ds); @@ -934,6 +1098,82 @@ int main(void) } } + /* <, =, D<, 2SWAP, 2OVER, DMAX and DMIN as written in section 5, against C. */ + for (unsigned i = 0; i < NVEC; i++) + for (unsigned j = 0; j < NVEC; j++) { + v4_cell a = vec[i], b = vec[j]; + CHECK(call(w_less, 2, a, b, 0) && left1(FLAG(a < b)), "< [%u,%u]", i, j); + CHECK(call(w_equal, 2, a, b, 0) && left1(FLAG(a == b)), "= [%u,%u]", i, j); + for (unsigned k = 0; k < NVEC; k++) + for (unsigned l = 0; l < NVEC; l++) { + v4_cell c = vec[k], d = vec[l]; + int lt = dlt((v4_ucell)a, (v4_ucell)b, (v4_ucell)c, (v4_ucell)d); + v4_cell sw[4], ov[6], mx[2], mn[2]; + sw[0] = c; sw[1] = d; sw[2] = a; sw[3] = b; + ov[0] = a; ov[1] = b; ov[2] = c; ov[3] = d; ov[4] = a; ov[5] = b; + mx[0] = lt ? c : a; mx[1] = lt ? d : b; + mn[0] = lt ? a : c; mn[1] = lt ? b : d; + CHECK(call4(w_dless, a, b, c, d) && left1(FLAG(lt)), "D< [%u,%u,%u,%u]", i, j, k, l); + { + v4_cell kp[5]; + kp[0] = a; kp[1] = b; kp[2] = c; kp[3] = d; kp[4] = FLAG(lt); + CHECK(call4(w_dltkeep, a, b, c, d) && leftk(kp, 5), "(D<) [%u,%u,%u,%u]", i, j, k, l); + } + CHECK(call4(w_2swap, a, b, c, d) && leftk(sw, 4), "2SWAP [%u,%u,%u,%u]", i, j, k, l); + CHECK(call4(w_2over, a, b, c, d) && leftk(ov, 6), "2OVER [%u,%u,%u,%u]", i, j, k, l); + CHECK(call4(w_dmax, a, b, c, d) && leftk(mx, 2), "DMAX [%u,%u,%u,%u]", i, j, k, l); + CHECK(call4(w_dmin, a, b, c, d) && leftk(mn, 2), "DMIN [%u,%u,%u,%u]", i, j, k, l); + } + } + + /* Q.=, Q.<, Q.>, Q.0=, Q.MAX, Q.MIN (section 5.26: D=, D<, 2SWAP D<, D0=, + * DMAX, DMIN) on Q values, signed (D-8). v3 compares Q values unsigned, + * so v3 agrees only when both have the same sign; that is checked too. */ + { + static const uint64_t qc[] = { + 0, 1, 0x8000u, 0x10000u, 0x18000u, + (uint64_t)0 - 0x10000u, (uint64_t)0 - 1u, (uint64_t)0 - 0x8000u, + 0xFFFFFFFFu, 0x100000000u, (uint64_t)0 - 0x100000000u, + ((uint64_t)1 << 63) - 1u, (uint64_t)1 << 63 + }; + const unsigned nc = sizeof qc / sizeof qc[0]; + uint64_t x = 0x2545F4914F6CDD1Du; + unsigned doc_wrong = 0, doc_runs = 0; + + for (unsigned i = 0; i < nc * nc + 20000u; i++) { + uint64_t a, b; + v4_cell al, ah, bl, bh, w[2]; + int lt, gt, eq; + if (i < nc * nc) { a = qc[i / nc]; b = qc[i % nc]; } + else { + x ^= x << 13; x ^= x >> 7; x ^= x << 17; a = x; + x ^= x << 13; x ^= x >> 7; x ^= x << 17; b = x; + if (i & 1u) b = a; /* ties */ + if (i & 2u) b ^= (uint64_t)1 << (x >> 58); /* near */ + } + qcells(a, &al, &ah); + qcells(b, &bl, &bh); + lt = qs(a) < qs(b); gt = qs(a) > qs(b); eq = a == b; + + CHECK(call4(w_dequal, al, ah, bl, bh) && left1(FLAG(eq)), "Q.= [%u]", i); + CHECK(call4(w_dless, al, ah, bl, bh) && left1(FLAG(lt)), "Q.< [%u]", i); + CHECK(call4(w_qgt, al, ah, bl, bh) && left1(FLAG(gt)), "Q.> [%u]", i); + CHECK(call(w_d0equal, 2, al, ah, 0) && left1(FLAG(a == 0)), "Q.0= [%u]", i); + w[0] = lt ? bl : al; w[1] = lt ? bh : ah; + CHECK(call4(w_dmax, al, ah, bl, bh) && leftk(w, 2), "Q.MAX [%u]", i); + w[0] = lt ? al : bl; w[1] = lt ? ah : bh; + CHECK(call4(w_dmin, al, ah, bl, bh) && leftk(w, 2), "Q.MIN [%u]", i); + if ((a >> 63) == (b >> 63)) { /* v3 agrees here */ + CHECK(lt == (a < b) && gt == (a > b), "Q compare v3 [%u]", i); + } + + /* Q.> as section 5.26 writes it, SWAP D<: count wrong answers. */ + doc_runs++; + if (!(call4(w_qgt_doc, al, ah, bl, bh) && left1(FLAG(gt)))) doc_wrong++; + } + printf(" Q.> as written (SWAP D<): %u wrong of %u\n", doc_wrong, doc_runs); + } + /* Stack headroom of the two longest definitions. */ { static const v4_cell umstar_args[][4] = { @@ -983,6 +1223,21 @@ int main(void) headroom("D0=", w_d0equal, 2, two_args, 5, 1, &dh, &rh); headroom("D=", w_dequal, 4, dsub_args, 6, 1, &dh, &rh); CHECK(dh >= 0 && rh >= 0, "D= runs at all"); + static const v4_cell cmp_args[][4] = { + { 0, 0, 0, 0 }, { 1, 0, 2, 0 }, { 2, 0, 1, 0 }, { 0, -1, 0, 1 }, { 0, 1, 0, -1 }, + { 0, 5, 0, 7 }, { 0, 7, 0, 5 }, { -1, 0, 1, 0 }, { 1, 0, -1, 0 } + }; + headroom("<", w_less, 2, two_args, 5, 1, &dh, &rh); + headroom("=", w_equal, 2, two_args, 5, 1, &dh, &rh); + headroom("D<", w_dless, 4, cmp_args, 9, 1, &dh, &rh); + headroom("(D<)", w_dltkeep, 4, cmp_args, 9, 5, &dh, &rh); + headroom("2SWAP", w_2swap, 4, cmp_args, 9, 4, &dh, &rh); + headroom("2OVER", w_2over, 4, cmp_args, 9, 6, &dh, &rh); + headroom("DMAX", w_dmax, 4, cmp_args, 9, 2, &dh, &rh); + CHECK(dh >= 2 && rh >= 2, "DMAX leaves room"); + headroom("DMIN", w_dmin, 4, cmp_args, 9, 2, &dh, &rh); + CHECK(dh >= 2 && rh >= 2, "DMIN leaves room"); + headroom("Q.> (2SWAP D<)", w_qgt, 4, cmp_args, 9, 1, &dh, &rh); headroom("SM/REM", w_smrem, 3, smrem_args, 5, 2, &dh, &rh); CHECK(rh >= 1, "SM/REM leaves room for /MOD's return address"); headroom("/MOD", w_slashmod, 2, slashmod_args, 4, 2, &dh, &rh);