feat(v4.0.0): the mixed and double leftovers

M- M* M/MOD MOD */ */MOD, D0< D2* D2/ 2ROT, 2DROP and 2>R 2R@ 2R>,
beside UM* and SM/REM in test_foundation.c.  Executed on the golden
model at both cell widths against C and results recorded from the v3
binary.

M- widens n before negating it, so the most negative n is right.
*/MOD goes through a full double product.  D2/ is one +* step.
M- and M/MOD take the double in the standard order ( lo hi ), as M+
does; v3 took its low cell on top.

The foundation test's node is now full: 958 of the 960 words below its
variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-03 18:37:53 -04:00
co-authored by Claude Opus 5.5
parent 84c7711763
commit 481d484e93
2 changed files with 263 additions and 17 deletions
+16 -16
View File
@@ -147,7 +147,7 @@ IF body1 ELSE body2 THEN → if L1 drop body1 jump L2
times, as on the F18. `FOR ... UNEXT` is the same but the body must fit in one instruction word.
**Register conventions.** `A` and `B` are caller-saved. A word that uses them says so. Words in this
document that clobber `A`: `@ ! +! -! 2@ 2! C@ C! FILL ERASE MOVE COUNT CMOVE CMOVE> BLANK -TRAILING COMPARE SEARCH SCAN SKIP DECIMAL HEX OCTAL UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS HOLD SIGN # #S . .R U. U.R D. D.R ? DUMP Q.PRINT TYPE SEND RECV`. Words that clobber `B`:
document that clobber `A`: `@ ! +! -! 2@ 2! C@ C! D2/ M* FILL ERASE MOVE COUNT CMOVE CMOVE> BLANK -TRAILING COMPARE SEARCH SCAN SKIP DECIMAL HEX OCTAL UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS HOLD SIGN # #S . .R U. U.R D. D.R ? DUMP Q.PRINT TYPE SEND RECV`. Words that clobber `B`:
`COMPARE SEARCH Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS <# HOLD SIGN # #S #> . .R U. U.R D. D.R ? DUMP Q.PRINT EMIT CR SPACE SPACES TYPE SEND RECV`.
**Return-stack words** (`>R R> R@ 2>R 2R> 2R@ I J UNLOOP` and the loop runtimes) are always IN.
@@ -422,13 +422,13 @@ A double is two 32-bit cells on a mesh node.
| Word | Fate | v4 definition |
| --- | --- | --- |
| `M+` | CAP | `S>D D+` — executed on the golden model (2026-10-02). |
| `M-` | CAP | `NEGATE M+` |
| `M*` | CAP | `2DUP xor push ABS SWAP ABS UM* pop 0< IF DNEGATE THEN` |
| `M/MOD` | CAP | `SM/REM` |
| `MOD` | CAP | `/MOD drop` |
| `M-` | CAP | `( d n -- d )`: `S>D DNEGATE jump D+`, `S>D` by a sign test in line. `n` is widened before it is negated, so the most negative `n` is subtracted correctly; `NEGATE M+` would add it. Executed on the golden model (2026-10-03) against C and results recorded from the v3 binary (v3 took the double with its low cell on top; the standard order is kept here, as for `M+`). Leaves its caller 5 data cells and 5 return entries. |
| `M*` | CAP | `( n1 n2 -- d )`: `over over xor push -if A inv 1 + A: push -if B inv 1 + B: pop UM* pop -if P drop jump DNEGATE P: drop ;` — the unsigned product of the magnitudes, negated when the signs differ; the sign tests are native. Executed on the golden model (2026-10-03) against C and results recorded from the v3 binary. Leaves its caller 6 data cells and 4 return entries. Clobbers `A`. |
| `M/MOD` | CAP | `( d n -- rem quot )`: `jump SM/REM`. Truncating, the remainder with the dividend's sign, as v3. v3 took the double with its low cell on top (`dhigh dlow n`), the reverse of what its own `M*` leaves; the standard order is kept here, so `M* ... M/MOD` composes. Executed on the golden model (2026-10-03), including results recorded from the v3 binary with the double's cells exchanged. Leaves its caller 5 data cells and 3 return entries. |
| `MOD` | CAP | `/MOD drop`, with `/MOD`'s body in line: `push S>D pop SM/REM drop`. The remainder has the dividend's sign, as v3. Executed on the golden model (2026-10-03) against C and results recorded from the v3 binary. Leaves its caller 5 data cells and 2 return entries. Division by zero is unspecified, as for `/`. |
| `/MOD` | CAP | `push S>D pop SM/REM` — executed on the golden model (2026-10-02). |
| `*/` | CAP | `*/MOD NIP` |
| `*/MOD` | CAP | `push M* pop SM/REM` |
| `*/` | CAP | `*/MOD NIP`, as `push M* pop SM/REM push drop pop`. Executed on the golden model (2026-10-03). Leaves its caller 5 data cells and 2 return entries. |
| `*/MOD` | CAP | `( n1 n2 n3 -- rem quot )`: `push M* pop jump SM/REM` — the product is a full double, so the answer is exact whenever the quotient fits a cell. v3 multiplied in one cell at 64-bit cells and so was right only while `n1 * n2` fitted one; the two agree there. Executed on the golden model (2026-10-03) against the identity `quot * n3 + rem = n1 * n2` on every combination of the edge values, and results recorded from the v3 binary. Leaves its caller 5 data cells and 2 return entries. |
### 5.7 Double-cell numbers
@@ -440,22 +440,22 @@ A double is two 32-bit cells on a mesh node.
| `D-` | CAP | `DNEGATE D+` — executed on the golden model (2026-10-02). |
| `DABS` | CAP | `dup 0< IF DNEGATE THEN` — executed on the golden model (2026-10-02). |
| `D0=` | CAP | `OR 0=` — executed on the golden model (2026-10-02). |
| `D0<` | CAP | `NIP 0<` |
| `D0<` | CAP | `NIP 0<`, as `push drop pop jump 0<`. Executed on the golden model (2026-10-03), including results recorded from the v3 binary. |
| `D=` | CAP | `D- D0=` — executed on the golden model (2026-10-02). |
| `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` |
| `D2*` | CAP | `2* over -if P drop 1 + jump J P: drop J: push 2* pop` — the low cell's top bit enters the high cell; a native sign test in place of `over 0< NEGATE OR`. Executed on the golden model (2026-10-03), including results recorded from the v3 binary. |
| `D2/` | CAP | `push a! 0 pop +* push drop a pop` — one `+*` with `S = 0` is an exact arithmetic right shift of `T:A`, as in `Q.FROM-INT`. It replaces `dup 1 and push 2/ SWAP 1 RSHIFT pop IF MSB OR THEN SWAP`. Executed on the golden model (2026-10-03), including results recorded from the v3 binary. Clobbers `A`. |
| `2DROP` | IN | `drop drop` — executed on the golden model (2026-10-03). |
| `2DUP` | IN | `over over` — executed on the golden model (2026-10-03). |
| `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` |
| `2R@` | IN | `pop pop 2DUP push push SWAP` |
| `2ROT` | CAP | `push push 2SWAP pop pop jump 2SWAP`. Executed on the golden model (2026-10-03), including a result recorded from the v3 binary. With six cells of its own it leaves its caller 3 data cells and 1 return entry. |
| `2>R` | IN | `SWAP push push` — executed on the golden model (2026-10-03) with `2R@` and `2R>`; the high cell is on top of the return stack, as in v3. |
| `2R>` | IN | `pop pop SWAP` — see `2>R`. |
| `2R@` | IN | `pop pop 2DUP push push SWAP` — see `2>R`. |
### 5.8 Number formatting and output
+247 -1
View File
@@ -79,7 +79,8 @@ static v4_asm as;
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_star, w_slash, w_mplus, w_dminus, w_d0equal, w_dequal,
w_slashmod, w_star, w_slash, w_mminus, w_mstar, w_mslashmod, w_mod, w_starslashmod,
w_starslash, w_d0less, w_d2star, w_d2slash, w_2rot, w_2drop, t_2r, t_2rorder, w_mplus, w_dminus, w_d0equal, w_dequal,
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, w_qstar, w_d2starc, w_uqdiv, w_qslash, w_qexp, w_qsqrt, w_qlog,
w_qreduce, w_qsin, l_trig, w_qcos,
@@ -1434,7 +1435,101 @@ static void build(void)
O(DROP); O(FETCH_A); LIT(-256); O(AND); O(ADD); O(STORE_A); O(SEMI);
}
/* ---- the rest of 5.6 and 5.7 ---- */
#define FWD(op) v4_asm_branch_fwd(&as, V4_OP_##op)
#define HERE_(r) v4_asm_resolve(&as, (r), v4_asm_label(&as))
#define SWAP_INLINE() do { O(OVER); O(PUSH); O(PUSH); O(DROP); O(RPOP); O(RPOP); } while (0)
{
v4_asm_ref a, b;
/* : M- ( d n -- d ) S>D DNEGATE jump D+
* S>D in line: dup -if P drop -1 jump J P: drop 0 J:
* d - n as d + (-n) with n widened first, so the most negative n is
* subtracted correctly (NEGATE M+ would add it). */
w_mminus = v4_asm_label(&as);
O(DUP); a = FWD(MINUS_IF); O(DROP); LIT(-1); b = FWD(JUMP);
HERE_(a); O(DROP); LIT(0);
HERE_(b);
CALL(w_dnegate); v4_asm_branch(&as, V4_OP_JUMP, w_dplus);
/* : M* ( n1 n2 -- d )
* over over xor push R: sign of the product
* -if A inv 1 + A: push -if B inv 1 + B: pop |n1| |n2|
* UM* pop -if P drop jump DNEGATE P: drop ; */
w_mstar = v4_asm_label(&as);
O(OVER); O(OVER); O(XOR); O(PUSH);
a = FWD(MINUS_IF); O(INV); LIT(1); O(ADD); HERE_(a);
O(PUSH);
a = FWD(MINUS_IF); O(INV); LIT(1); O(ADD); HERE_(a);
O(RPOP);
CALL(w_umstar); O(RPOP); a = FWD(MINUS_IF);
O(DROP); v4_asm_branch(&as, V4_OP_JUMP, w_dnegate);
HERE_(a); O(DROP); O(SEMI);
/* : M/MOD ( d n -- rem quot ) jump SM/REM */
w_mslashmod = v4_asm_label(&as);
v4_asm_branch(&as, V4_OP_JUMP, w_smrem);
/* : MOD ( n1 n2 -- rem ) /MOD drop, /MOD's body in line */
w_mod = v4_asm_label(&as);
O(PUSH); CALL(w_s2d); O(RPOP); CALL(w_smrem); O(DROP); O(SEMI);
// : */MOD ( n1 n2 n3 -- rem quot ) push M* pop jump SM/REM
w_starslashmod = v4_asm_label(&as);
O(PUSH); CALL(w_mstar); O(RPOP); v4_asm_branch(&as, V4_OP_JUMP, w_smrem);
// : */ ( n1 n2 n3 -- quot ) push M* pop SM/REM push drop pop ; that is, */MOD NIP
w_starslash = v4_asm_label(&as);
O(PUSH); CALL(w_mstar); O(RPOP); CALL(w_smrem); O(PUSH); O(DROP); O(RPOP); O(SEMI);
/* : D0< ( d -- flag ) push drop pop jump 0< NIP 0< */
w_d0less = v4_asm_label(&as);
O(PUSH); O(DROP); O(RPOP); v4_asm_branch(&as, V4_OP_JUMP, w_zless);
/* : D2* ( d -- 2d )
* 2* over -if P drop 1 + jump J P: drop J: push 2* pop ;
* the low cell's top bit enters the high cell. */
w_d2star = v4_asm_label(&as);
O(TWO_STAR); O(OVER); a = FWD(MINUS_IF); O(DROP); LIT(1); O(ADD); b = FWD(JUMP);
HERE_(a); O(DROP);
HERE_(b);
O(PUSH); O(TWO_STAR); O(RPOP); O(SEMI);
/* : D2/ ( d -- d/2 ) push a! 0 pop +* push drop a pop ;
* one +* with S = 0 is an exact arithmetic right shift of T:A (as in
* Q.FROM-INT). Clobbers A. */
w_d2slash = v4_asm_label(&as);
O(PUSH); O(BANG_A); LIT(0); O(RPOP); O(MUL_STEP); O(PUSH); O(DROP); O(PUSH_A); O(RPOP); O(SEMI);
/* : 2ROT ( d1 d2 d3 -- d2 d3 d1 ) push push 2SWAP pop pop jump 2SWAP */
w_2rot = v4_asm_label(&as);
O(PUSH); O(PUSH); CALL(w_2swap); O(RPOP); O(RPOP); v4_asm_branch(&as, V4_OP_JUMP, w_2swap);
/* 2DROP is drop drop */
w_2drop = v4_asm_label(&as);
O(DROP); O(DROP); O(SEMI);
/* ( d -- d d ) 2>R 2R@ 2R> in line:
* 2>R is SWAP push push
* 2R@ is pop pop 2DUP push push SWAP
* 2R> is pop pop SWAP */
t_2r = v4_asm_label(&as);
SWAP_INLINE(); O(PUSH); O(PUSH);
O(RPOP); O(RPOP); O(OVER); O(OVER); O(PUSH); O(PUSH); SWAP_INLINE();
O(RPOP); O(RPOP); SWAP_INLINE();
O(SEMI);
/* ( lo hi -- hi lo ) 2>R R> R> the high cell is on top of R, as in v3 */
t_2rorder = v4_asm_label(&as);
SWAP_INLINE(); O(PUSH); O(PUSH); O(RPOP); O(RPOP); O(SEMI);
}
#undef SWAP_INLINE
#undef HERE_
#undef FWD
CHECK(v4_asm_ok(&as), "foundation words assemble");
CHECK(v4_asm_label(&as) <= BYTES, "code stays below the variables");
printf(" code: %ld of %u words\n", (long)v4_asm_label(&as) - 16, (unsigned)V4_NODE_WORDS);
}
/* Call `word` with a canary and up to three arguments on fresh stacks. */
@@ -1547,6 +1642,51 @@ static int smrem_exact(v4_cell q, v4_cell nn, v4_cell r)
return call(w_smrem, 3, (v4_cell)dl, (v4_cell)dh, nn) && left2(r, q);
}
/* Six arguments, for 2ROT. */
static int call6(v4_cell word, const v4_cell *a, unsigned dfill, unsigned rfill)
{
unsigned i;
v4_dstack_reset(&n.ds);
v4_rstack_reset(&n.rs);
v4_exec_reset(&es);
for (i = 0; i < dfill; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i));
v4_dstack_push(&n.ds, CANARY);
for (i = 0; i < 6; i++) v4_dstack_push(&n.ds, a[i]);
for (i = 0; i < rfill; i++) v4_rstack_push(&n.rs, (v4_cell)(0x6B000000 + i));
return v4_test_call(&n, &es, &h, word, 4000000) > 0;
}
static int rot6_ok(const v4_cell *a, unsigned dfill, unsigned rfill)
{
static const unsigned from[6] = { 2, 3, 4, 5, 0, 1 }; /* d2 d3 d1 */
unsigned i;
if (!call6(w_2rot, a, dfill, rfill)) return 0;
for (i = 6; i-- > 0; ) if (v4_dstack_pop(&n.ds) != a[from[i]]) return 0;
if (v4_dstack_pop(&n.ds) != CANARY) return 0;
for (i = dfill; i-- > 0; ) if (v4_dstack_pop(&n.ds) != (v4_cell)(0x5A000000 + i)) return 0;
for (i = rfill; i-- > 0; ) if (v4_rstack_pop(&n.rs) != (v4_cell)(0x6B000000 + i)) return 0;
return 1;
}
/* n1 * n2 / n3 through a double product. Returns 1 when the truncated
* quotient fits a cell and (r, q) is it: q*n3 + r = n1*n2, |r| < |n3|, r zero
* or of the product's sign; 0 when it fits and (r, q) is wrong; -1 when it
* does not fit (unspecified, as for SM/REM). */
static int starslash_ref(v4_cell n1, v4_cell n2, v4_cell n3, v4_cell r, v4_cell q)
{
v4_ucell pl, ph, al, ah, ql, qh, sl, sh, un3 = n3 < 0 ? 0u - (v4_ucell)n3 : (v4_ucell)n3, ur;
int pneg;
smul(n1, n2, &pl, &ph);
pneg = (ph & V4_MSB) != 0;
al = pl; ah = ph;
if (pneg) dneg(pl, ph, &al, &ah);
if (ah >= (V4_MSB >> 1)) return -1;
if (((ah << 1) | (al >> (V4_CELL_BITS - 1))) >= un3) return -1; /* |p| >= |n3| * 2^(N-1) */
smul(q, n3, &ql, &qh);
dadd(ql, qh, (v4_ucell)r, r < 0 ? MAXU : 0u, &sl, &sh);
ur = r < 0 ? 0u - (v4_ucell)r : (v4_ucell)r;
return sl == pl && sh == ph && ur < un3 && (r == 0 || (r < 0) == pneg);
}
/* Q48.16 (section 5.26). v3's Q.+ and Q.- are uint64_t a + b and a - b,
* wrapping (v3/include/q48_16.h). Here a Q value is a double: at 32-bit
* cells its two halves, at 64-bit cells the value sign-extended (D-8). */
@@ -2098,6 +2238,79 @@ int main(void)
}
}
/* The rest of 5.6 and 5.7. "v3:" marks results recorded from the v3
* binary on 2026-10-03; v3's M- and M/MOD took the double with its low
* cell on top, so their arguments are in v4's order here. */
CHECK(call(w_mstar, 2, 6, 7, 0) && left2(42, 0), "v3: 6 7 M*");
CHECK(call(w_mstar, 2, -6, 7, 0) && left2(-42, -1), "v3: -6 7 M*");
CHECK(call(w_mod, 2, 17, 5, 0) && left1(2), "v3: 17 5 MOD");
CHECK(call(w_mod, 2, -17, 5, 0) && left1(-2), "v3: -17 5 MOD");
CHECK(call(w_mod, 2, 17, -5, 0) && left1(2), "v3: 17 -5 MOD");
CHECK(call(w_starslash, 3, 7, 3, 2) && left1(10), "v3: 7 3 2 */");
CHECK(call(w_starslash, 3, -7, 3, 2) && left1(-10), "v3: -7 3 2 */");
CHECK(call(w_starslashmod, 3, 7, 3, 2) && left2(1, 10), "v3: 7 3 2 */MOD");
CHECK(call(w_starslashmod, 3, -7, 3, 2) && left2(-1, -10), "v3: -7 3 2 */MOD");
CHECK(call(w_d0less, 2, 5, 0, 0) && left1(0), "v3: 5 0 D0<");
CHECK(call(w_d0less, 2, 5, -1, 0) && left1(-1), "v3: 5 -1 D0<");
CHECK(call(w_d2star, 2, 3, 0, 0) && left2(6, 0), "v3: 3 0 D2*");
CHECK(call(w_d2star, 2, -1, 0, 0) && left2(-2, 1), "v3: -1 0 D2*");
CHECK(call(w_d2slash, 2, 6, 0, 0) && left2(3, 0), "v3: 6 0 D2/");
CHECK(call(w_d2slash, 2, 1, 1, 0) && left2((v4_cell)V4_MSB, 0), "v3: 1 1 D2/");
CHECK(call(w_d2slash, 2, -4, -1, 0) && left2(-2, -1), "v3: -4 -1 D2/");
CHECK(call(w_mslashmod, 3, 100, 0, 7) && left2(2, 14), "v3: 100 7 M/MOD");
CHECK(call(w_mslashmod, 3, -100, -1, 7) && left2(-2, -14), "v3: -100 7 M/MOD");
CHECK(call(w_mminus, 3, 100, 0, 7) && left2(93, 0), "v3: 100 7 M-");
CHECK(call(w_mminus, 3, 100, 0, -7) && left2(107, 0), "v3: 100 -7 M-");
CHECK(call(w_2drop, 3, 1, 2, 3) && left1(1), "v3: 1 2 3 2DROP");
CHECK(call(t_2r, 2, 1, 2, 0) && v4_dstack_pop(&n.ds) == 2 && v4_dstack_pop(&n.ds) == 1 && left2(1, 2),
"v3: 1 2 2>R 2R@ 2R>");
CHECK(call(t_2rorder, 2, 1, 2, 0) && left2(2, 1), "2>R leaves the high cell on top of R");
{
static const v4_cell six[6] = { 1, 2, 3, 4, 5, 6 };
CHECK(rot6_ok(six, 0, 0), "v3: 1 2 3 4 5 6 2ROT");
}
for (unsigned i = 0; i < NVEC; i++)
for (unsigned j = 0; j < NVEC; j++) {
v4_cell a = vec[i], b = vec[j];
v4_ucell ua = (v4_ucell)a, ub = (v4_ucell)b, pl, ph;
smul(a, b, &pl, &ph);
CHECK(call(w_mstar, 2, a, b, 0) && left2((v4_cell)pl, (v4_cell)ph), "M* [%u,%u]", i, j);
CHECK(call(w_d0less, 2, a, b, 0) && left1(FLAG(b < 0)), "D0< [%u,%u]", i, j);
CHECK(call(w_d2star, 2, a, b, 0)
&& left2((v4_cell)(ua << 1), (v4_cell)((ub << 1) | (ua >> (V4_CELL_BITS - 1)))), "D2* [%u,%u]", i, j);
CHECK(call(w_d2slash, 2, a, b, 0)
&& left2((v4_cell)((ua >> 1) | (ub << (V4_CELL_BITS - 1))), (v4_cell)((ub >> 1) | (ub & V4_MSB))),
"D2/ [%u,%u]", i, j);
CHECK(call(w_2drop, 3, 77, a, b) && left1(77), "2DROP [%u,%u]", i, j);
CHECK(call(t_2r, 2, a, b, 0) && v4_dstack_pop(&n.ds) == b && v4_dstack_pop(&n.ds) == a && left2(a, b),
"2>R 2R@ 2R> [%u,%u]", i, j);
if (b != 0 && !(a == (v4_cell)V4_MSB && b == -1)) {
CHECK(call(w_mod, 2, a, b, 0) && left1(a % b), "MOD [%u,%u]", i, j);
CHECK(call(w_mslashmod, 3, a, a < 0 ? -1 : 0, b) && left2(a % b, a / b), "M/MOD [%u,%u]", i, j);
}
for (unsigned k = 0; k < NVEC; k++) {
v4_cell c = vec[k], r, q;
int ok;
{
v4_cell six[6];
six[0] = a; six[1] = b; six[2] = c; six[3] = vec[(i + 5) % NVEC]; six[4] = vec[(j + 7) % NVEC]; six[5] = vec[(k + 3) % NVEC];
CHECK(rot6_ok(six, 0, 0), "2ROT [%u,%u,%u]", i, j, k);
}
if (c == 0) continue;
if (!call(w_starslashmod, 3, a, b, c)) { CHECK(0, "*/MOD returns [%u,%u,%u]", i, j, k); continue; }
q = n.ds.t; r = n.ds.s;
ok = starslash_ref(a, b, c, r, q);
CHECK(ok != 0, "*/MOD [%u,%u,%u]", i, j, k);
if (ok == 1) {
CHECK(left2(r, q), "*/MOD leaves two cells [%u,%u,%u]", i, j, k);
CHECK(call(w_starslash, 3, a, b, c) && left1(q), "*/ [%u,%u,%u]", i, j, k);
}
}
}
/* M/MOD is SM/REM: a double that does not fit a cell */
CHECK(call(w_mslashmod, 3, 0, 5, 10) && n.ds.s == 0
&& (v4_ucell)n.ds.t == (v4_ucell)1 << (V4_CELL_BITS - 1), "M/MOD of 5 * 2^N by 10");
/* SM/REM: every quotient, divisor and in-range remainder sign drawn from
* the edge vectors, then pseudo-random. */
for (unsigned i = 0; i < NVEC; i++)
@@ -2163,6 +2376,13 @@ int main(void)
dadd(ua, ub, (v4_ucell)c, c < 0 ? MAXU : 0u, &rl, &rh);
CHECK(call(w_mplus, 3, vec[i], vec[j], c) && left2((v4_cell)rl, (v4_cell)rh),
"M+ [%u,%u,%u]", i, j, k);
{
v4_ucell nl, nh, dl, dh;
dneg((v4_ucell)c, c < 0 ? MAXU : 0u, &nl, &nh);
dadd(ua, ub, nl, nh, &dl, &dh);
CHECK(call(w_mminus, 3, vec[i], vec[j], c) && left2((v4_cell)dl, (v4_cell)dh),
"M- [%u,%u,%u]", i, j, k);
}
for (unsigned l = 0; l < NVEC; l++) {
v4_ucell nl, nh;
dneg((v4_ucell)vec[k], (v4_ucell)vec[l], &nl, &nh);
@@ -2803,6 +3023,32 @@ int main(void)
{ (v4_cell)V4_MSB, 0, (v4_cell)V4_MSB, 0 }, { 1, 0, 2, 0 }
};
headroom("M+", w_mplus, 3, mplus_args, 4, 2, &dh, &rh);
headroom("M-", w_mminus, 3, mplus_args, 4, 2, &dh, &rh);
CHECK(dh >= 4 && rh >= 4, "M- leaves room");
{
static const v4_cell six[6] = { 1, 2, 3, 4, 5, 6 };
static const v4_cell d1_args[][4] = { { 5, -1, 0, 0 }, { -1, 0, 0, 0 }, { 1, 1, 0, 0 } };
static const v4_cell ms_args[][4] = { { 7, 3, 0, 0 }, { -7, 3, 0, 0 }, { -7, -3, 0, 0 } };
static const v4_cell ss_args[][4] = { { 7, 3, 2, 0 }, { -7, 3, 2, 0 }, { 7, -3, -2, 0 } };
int d, r;
headroom("M*", w_mstar, 2, ms_args, 3, 2, &dh, &rh);
CHECK(dh >= 4 && rh >= 4, "M* leaves room");
headroom("M/MOD", w_mslashmod, 3, smrem_args, 5, 2, &dh, &rh);
headroom("MOD", w_mod, 2, slashmod_args, 4, 1, &dh, &rh);
CHECK(dh >= 3 && rh >= 2, "MOD leaves room");
headroom("*/MOD", w_starslashmod, 3, ss_args, 3, 2, &dh, &rh);
CHECK(dh >= 3 && rh >= 2, "*/MOD leaves room");
headroom("*/", w_starslash, 3, ss_args, 3, 1, &dh, &rh);
CHECK(dh >= 3 && rh >= 2, "*/ leaves room");
headroom("D0<", w_d0less, 2, d1_args, 3, 1, &dh, &rh);
headroom("D2*", w_d2star, 2, d1_args, 3, 2, &dh, &rh);
headroom("D2/", w_d2slash, 2, d1_args, 3, 2, &dh, &rh);
CHECK(dh >= 5 && rh >= 6, "D2/ leaves room");
for (d = 0; d < V4_DATA_DEPTH; d++) if (!rot6_ok(six, (unsigned)d + 1u, 0)) break;
for (r = 0; r < V4_RET_DEPTH; r++) if (!rot6_ok(six, 0, (unsigned)r + 1u)) break;
printf(" 2ROT headroom: data %d below canary, return %d below its return address\n", d, r);
CHECK(d >= 2 && r >= 1, "2ROT leaves room");
}
headroom("D-", w_dminus, 4, dsub_args, 6, 2, &dh, &rh);
headroom("D0=", w_d0equal, 2, two_args, 5, 1, &dh, &rh);
headroom("D=", w_dequal, 4, dsub_args, 6, 1, &dh, &rh);