diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index bf5985b5..a2c96efd 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -390,7 +390,7 @@ A double is two 32-bit cells on a mesh node. | Word | Fate | v4 definition | | --- | --- | --- | -| `M+` | CAP | `S>D D+` | +| `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` | @@ -406,11 +406,11 @@ A double is two 32-bit cells on a mesh node. | `S>D` | CAP | `dup 0<` — executed on the golden model (2026-10-02). | | `D+` | CAP | `push over push push drop pop over over xor -if L1 drop + -if C1 jump C0 L1: drop over -if L2 drop + jump C1 L2: drop + C0: pop pop + ; C1: pop pop + 1 + ;` — call-free. The carry out of the low cells is found by sign tests: if their top bits differ, there is a carry exactly when the sum's top bit is clear; if they match, exactly when both are set. Executed on the golden model (2026-10-02); leaves its caller 6 data cells under its arguments and 5 return entries. Replaces `push SWAP push over + 2DUP U> ROT drop NEGATE pop pop + +`, which was exact but left 1 return entry. | | `DNEGATE` | CAP | `inv over if L1 drop push inv 1 + pop ; L1: drop 1 + ;` — call-free: `~d + 1`, carrying into the high cell exactly when the low cell is 0. Executed on the golden model (2026-10-02). Replaces `inv SWAP inv SWAP 1 0 D+`, which left a caller no return-stack room, so `DABS` could not run at all. | -| `D-` | CAP | `DNEGATE D+` | +| `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=` | +| `D0=` | CAP | `OR 0=` — executed on the golden model (2026-10-02). | | `D0<` | CAP | `NIP 0<` | -| `D=` | CAP | `D- D0=` | +| `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` | diff --git a/v4/tests/test_foundation.c b/v4/tests/test_foundation.c index ecea80b0..d7ae7676 100644 --- a/v4/tests/test_foundation.c +++ b/v4/tests/test_foundation.c @@ -14,7 +14,8 @@ * call-free versions in sections 4 and 5.7; SM/REM is checked on dividends * built as q*n + r. D+ is the call-free version in 5.7, checked against C * over every quadruple of the edge vectors and pseudo-random pairs. /MOD, - * U>, ABS, S>D and DABS are as written in section 5 and checked against C. Every one is also probed for how much of + * U>, ABS, S>D, DABS, M+, D-, D0= and D= are as written in section 5 and + * checked against C. Every one is also probed for how much of * the 10- and 9-deep circular stacks (D-2) it leaves to its caller. * * Every call is made with a canary under the arguments, and the canary must @@ -41,7 +42,7 @@ 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_slashmod, w_mplus, w_dminus, w_d0equal, w_dequal; #define O(name) v4_asm_op(&as, V4_OP_##name) #define LIT(v) v4_asm_lit(&as, (v4_cell)(v)) @@ -325,6 +326,22 @@ static void build(void) w_slashmod = v4_asm_label(&as); O(PUSH); CALL(w_s2d); O(RPOP); CALL(w_smrem); O(SEMI); + /* : M+ S>D D+ ; 5.6 */ + w_mplus = v4_asm_label(&as); + CALL(w_s2d); CALL(w_dplus); O(SEMI); + + /* : D- DNEGATE D+ ; 5.7 */ + w_dminus = v4_asm_label(&as); + CALL(w_dnegate); CALL(w_dplus); O(SEMI); + + /* : D0= OR 0= ; 5.7 */ + w_d0equal = v4_asm_label(&as); + CALL(w_or); CALL(w_zequal); O(SEMI); + + /* : D= D- D0= ; 5.7 */ + w_dequal = v4_asm_label(&as); + CALL(w_dminus); CALL(w_d0equal); O(SEMI); + CHECK(v4_asm_ok(&as), "foundation words assemble"); } @@ -664,6 +681,50 @@ int main(void) } } + /* M+, D-, D0= and D= against C: every edge-vector combination, then + * pseudo-random doubles. */ + for (unsigned i = 0; i < NVEC; i++) + for (unsigned j = 0; j < NVEC; j++) { + v4_ucell ua = (v4_ucell)vec[i], ub = (v4_ucell)vec[j], rl, rh; + CHECK(call(w_d0equal, 2, vec[i], vec[j], 0) && left1(FLAG(ua == 0 && ub == 0)), + "D0= [%u,%u]", i, j); + for (unsigned k = 0; k < NVEC; k++) { + v4_cell c = vec[k]; + 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); + for (unsigned l = 0; l < NVEC; l++) { + v4_ucell nl, nh; + dneg((v4_ucell)vec[k], (v4_ucell)vec[l], &nl, &nh); + dadd(ua, ub, nl, nh, &rl, &rh); + CHECK(call4(w_dminus, vec[i], vec[j], vec[k], vec[l]) + && left2((v4_cell)rl, (v4_cell)rh), "D- [%u,%u,%u,%u]", i, j, k, l); + CHECK(call4(w_dequal, vec[i], vec[j], vec[k], vec[l]) + && left1(FLAG(i == k && j == l)), "D= [%u,%u,%u,%u]", i, j, k, l); + } + } + } + { + v4_ucell x = (v4_ucell)0x5851F42Du, v[4], rl, rh, nl, nh; + for (unsigned i = 0; i < 20000; i++) { + for (unsigned k = 0; k < 4; k++) { x ^= x << 13; x ^= x >> 7; x ^= x << 17; v[k] = x; } + if (i & 1u) v[2] = v[0]; /* equal low cells */ + if (i & 2u) v[3] = v[1]; /* equal high cells */ + if (i & 4u) v[2] = 0u - v[0]; /* low sum wraps */ + dadd(v[0], v[1], v[2], (v4_cell)v[2] < 0 ? MAXU : 0u, &rl, &rh); + CHECK(call(w_mplus, 3, (v4_cell)v[0], (v4_cell)v[1], (v4_cell)v[2]) + && left2((v4_cell)rl, (v4_cell)rh), "M+ random [%u]", i); + dneg(v[2], v[3], &nl, &nh); + dadd(v[0], v[1], nl, nh, &rl, &rh); + CHECK(call4(w_dminus, (v4_cell)v[0], (v4_cell)v[1], (v4_cell)v[2], (v4_cell)v[3]) + && left2((v4_cell)rl, (v4_cell)rh), "D- random [%u]", i); + CHECK(call4(w_dequal, (v4_cell)v[0], (v4_cell)v[1], (v4_cell)v[2], (v4_cell)v[3]) + && left1(FLAG(v[0] == v[2] && v[1] == v[3])), "D= random [%u]", i); + CHECK(call(w_d0equal, 2, (v4_cell)(v[0] & v[2]), (v4_cell)(v[1] & v[3]), 0) + && left1(FLAG((v[0] & v[2]) == 0 && (v[1] & v[3]) == 0)), "D0= random [%u]", i); + } + } + /* Stack headroom of the two longest definitions. */ { static const v4_cell umstar_args[][4] = { @@ -701,6 +762,18 @@ int main(void) CHECK(rh >= 5, "D+ leaves 5 return entries"); headroom("DNEGATE", w_dnegate, 2, two_args, 5, 2, &dh, &rh); headroom("DABS", w_dabs, 2, two_args, 5, 2, &dh, &rh); + static const v4_cell mplus_args[][4] = { + { -1, 0, 1, 0 }, { 5, 7, -9, 0 }, { 0, 0, -1, 0 }, { (v4_cell)V4_MSB, 0, (v4_cell)V4_MSB, 0 } + }; + static const v4_cell dsub_args[][4] = { + { 0, 0, 1, 0 }, { 5, 7, 9, 11 }, { -1, -1, -1, -1 }, { 0, 1, 0, 1 }, + { (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("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); + CHECK(dh >= 0 && rh >= 0, "D= runs at all"); 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);