diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index 64be66ee..835f669c 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -212,14 +212,23 @@ dependency order. \ restore the bits the two halvings dropped and the unsigned reading of \ both top bits. Exact over the full range; proven on the golden model. : UM* ( u1 u2 -- ulo uhi ) - over 0< over and push \ R: u1<0 ? u2 : 0 - over over 0< and 1 and pop + push \ R: c_hi - over over and 1 and push \ R: c_hi c_lo - over 1 and NEGATE over 2/ and push \ R: c_hi c_lo t0 - a! 2/ pop \ s t0 A: u2 - 31 FOR +* UNEXT \ s hi A: lo - NIP a 2* pop + SWAP \ lo' hi - 2* a 0< NEGATE + pop + ; \ lo' hi' + over 1 and inv 1 + over 2/ and \ u1 u2 t0 t0 = u2 2/ if u1 odd + over a! 31 push \ A: u2 R: loop count (FOR's push, early) + push over 2/ pop \ u1 u2 s t0 s = u1 2/ + L: +* unext \ u1 u2 s hi A: lo + push drop pop \ u1 u2 hi + 2* a -if L0 drop 1 + jump L1 L0: drop L1: push \ R: 2hi + top bit of lo + over -if L2 drop dup jump L3 L2: drop 0 L3: push \ R: + (u1<0 ? u2 : 0) + dup -if L4 drop over 1 and jump L5 L4: drop 0 L5: \ u2<0 ? u1&1 : 0 + pop + pop + push \ u1 u2 R: hi' + and 1 and a 2* + pop ; \ lo' hi' +\ u1 and u2 wait on the data stack under the loop (+* touches only T, S and +\ A), so the corrections are made after it, with -if in place of 0< calls, +\ and the return stack holds at most two temporaries. The loop count is +\ pushed before s is made, so the data stack never holds more than four. +\ Leaves its caller 6 data cells under its arguments and 6 return entries +\ (revised 2026-10-02; the first full-range version parked its three +\ corrections on the return stack and left 4). \ ---- divide: 32-step restoring division, divisor held in A -------------- \ Each step shifts hi:lo left one bit and, when the shifted-out bit of hi was @@ -251,7 +260,7 @@ dependency order. \ (uhi >= ud, including ud = 0) the result is unspecified, as in FORTH-79; it \ always terminates after 32 steps. Stack use (D-2): up to 6 data cells may \ lie under the three arguments and up to 6 return-stack entries under its -\ return address. For UM* the same limits are 6 and 4. This replaces a +\ return address. For UM* the same limits are 6 and 6. This replaces a \ first version built on U<, 0<, SWAP and OR calls, which was also exact but \ left only 3 and 3 (too few for SM/REM inside /MOD) and stepped about five \ times as many instruction words. @@ -647,7 +656,7 @@ word. Per D-10 it occupies two cells on a 64-bit node too. Per D-8, v4 Q values | Word | Fate | Notes | | --- | --- | --- | | `Q.+` `Q.-` | CAP | `D+`, `D-` — executed on the golden model (2026-10-02) against v3's `q48_add`/`q48_sub` (`uint64_t` wrapping): bit-for-bit at 32-bit cells; at 64-bit cells the low cell is v3's, per D-10. | -| `Q.*` | CAP | `2OVER drop over 0< over and push over UM* pop - push push push over pop UM* drop pop pop push SWAP pop + push push over 0< over and pop pop push SWAP pop SWAP - push push over over UM* pop pop D+ push push push drop pop UM* 0 pop pop D+ push dup push Q.TO-INT pop pop Q.TO-INT` — `floor(a*b / 2^16)`, signed (D-8), cut to two cells. Cells 0–2 of the product come from the four unsigned cell products `a0*b0`, `a0*b1`, `a1*b0`, `a1*b1` (low cell only); reading `a1` and `b1` as signed takes `(a1<0 ? b0 : 0) + (b1<0 ? a0 : 0)` off cell 2; the result is cells 0–2 shifted right 16 with `Q.TO-INT` twice. Executed on the golden model (2026-10-02) against an independent limb-by-limb reference, and against v3's `q48_mul` for non-negative operands. **Stack use is tight:** it leaves its caller 2 data cells under its arguments and 1 return entry, because `UM*` itself parks three values on the return stack; to be improved. Clobbers `A`. | +| `Q.*` | CAP | `2OVER drop over 0< over and push over UM* pop - push push push over pop UM* drop pop pop push SWAP pop + push push over 0< over and pop pop push SWAP pop SWAP - push push over over UM* pop pop D+ push push push drop pop UM* 0 pop pop D+ push dup push Q.TO-INT pop pop Q.TO-INT` — `floor(a*b / 2^16)`, signed (D-8), cut to two cells. Cells 0–2 of the product come from the four unsigned cell products `a0*b0`, `a0*b1`, `a1*b0`, `a1*b1` (low cell only); reading `a1` and `b1` as signed takes `(a1<0 ? b0 : 0) + (b1<0 ? a0 : 0)` off cell 2; the result is cells 0–2 shifted right 16 with `Q.TO-INT` twice. Executed on the golden model (2026-10-02) against an independent limb-by-limb reference, and against v3's `q48_mul` for non-negative operands. **Stack use is tight:** it leaves its caller 2 data cells under its arguments and 2 return entries (1 before `UM*` was revised); it holds all four input cells across its `UM*` calls. To be improved. Clobbers `A`. | | `Q./` | CAP | Shifted long division. **Division by zero sets an error** (v3 returned 0 silently). | | `Q.ABS` `Q.NEG` | CAP | `DABS`, `DNEGATE` — executed on the golden model (2026-10-02) against v3's `q48_abs` and `0 - q`: bit-for-bit at 32-bit cells, including v3's wrap of Q min to itself; at 64-bit cells the low cell is v3's and the high cell is the true sign, per D-10. | | `Q.LOG` `Q.EXP` `Q.SQRT` `Q.SIN` `Q.COS` | CAP | Algorithms ported from `q48_words.c`; the hosted C versions are the golden model. | diff --git a/v4/tests/test_foundation.c b/v4/tests/test_foundation.c index bc5aeef3..46171992 100644 --- a/v4/tests/test_foundation.c +++ b/v4/tests/test_foundation.c @@ -134,31 +134,57 @@ static void build(void) O(DROP); CALL(w_minus); CALL(w_zless); O(SEMI); /* : UM* ( u1 u2 -- ulo uhi ) section 4, D-3 - * over 0< over and push R: m1 ? u2 : 0 - * over over 0< and 1 and pop + push R: c_hi - * over over and 1 and push R: c_hi c_lo - * over 1 and NEGATE over 2/ and push R: c_hi c_lo t0 - * a! 2/ pop s t0 A: u2 - * 31 FOR +* UNEXT s hi A: lo - * NIP a 2* pop + SWAP lo' hi - * 2* a 0< NEGATE + pop + ; lo' hi' - * NEGATE is placed in line. 31 is the cell width less one; the loop body - * is the start of its own word so that unext restarts it. */ - w_umstar = v4_asm_label(&as); - O(OVER); CALL(w_zless); O(OVER); O(AND); O(PUSH); - O(OVER); O(OVER); CALL(w_zless); O(AND); LIT(1); O(AND); - O(RPOP); O(ADD); O(PUSH); - O(OVER); O(OVER); O(AND); LIT(1); O(AND); O(PUSH); - O(OVER); LIT(1); O(AND); O(INV); LIT(1); O(ADD); - O(OVER); O(TWO_SLASH); O(AND); O(PUSH); - O(BANG_A); O(TWO_SLASH); O(RPOP); - LIT(V4_CELL_BITS - 1); O(PUSH); - (void)v4_asm_label(&as); - O(MUL_STEP); O(UNEXT); - CALL(w_nip); - O(PUSH_A); O(TWO_STAR); O(RPOP); O(ADD); CALL(w_swap); - O(TWO_STAR); O(PUSH_A); CALL(w_zless); O(INV); LIT(1); O(ADD); O(ADD); - O(RPOP); O(ADD); O(SEMI); + * over 1 and inv 1 + over 2/ and u1 u2 t0 t0 = u2 2/ if u1 odd + * over a! 31 push R: loop count (FOR's push, early) + * push over 2/ pop u1 u2 s t0 A: u2, s = u1 2/ + * L: +* unext u1 u2 s hi A: lo + * push drop pop u1 u2 hi + * 2* a -if L0 drop 1 + jump L1 L0: drop L1: push R: 2hi + top bit of lo + * over -if L2 drop dup jump L3 L2: drop 0 L3: push R: + (u1<0 ? u2 : 0) + * dup -if L4 drop over 1 and jump L5 L4: drop 0 L5: u1 u2 (u2<0 ? u1&1 : 0) + * pop + pop + push u1 u2 R: hi' + * and 1 and a 2* + pop ; lo' hi' + * u1 and u2 wait on the data stack under the loop (+* touches only T, S + * and A), so the corrections are made afterwards and the return stack + * holds at most two temporaries. 31 is the cell width less one, pushed + * before s is made so the data stack never holds more than four; the loop + * body is the start of its own word so that unext restarts it. */ + { + v4_asm_ref l0, l1, l2, l3, l4, l5; + w_umstar = v4_asm_label(&as); + O(OVER); LIT(1); O(AND); O(INV); LIT(1); O(ADD); O(OVER); O(TWO_SLASH); O(AND); + O(OVER); O(BANG_A); LIT(V4_CELL_BITS - 1); O(PUSH); + O(PUSH); O(OVER); O(TWO_SLASH); O(RPOP); + (void)v4_asm_label(&as); + O(MUL_STEP); O(UNEXT); + O(PUSH); O(DROP); O(RPOP); + O(TWO_STAR); O(PUSH_A); + l0 = v4_asm_branch_fwd(&as, V4_OP_MINUS_IF); + O(DROP); LIT(1); O(ADD); + l1 = v4_asm_branch_fwd(&as, V4_OP_JUMP); + v4_asm_resolve(&as, l0, v4_asm_label(&as)); + O(DROP); + v4_asm_resolve(&as, l1, v4_asm_label(&as)); + O(PUSH); + O(OVER); + l2 = v4_asm_branch_fwd(&as, V4_OP_MINUS_IF); + O(DROP); O(DUP); + l3 = v4_asm_branch_fwd(&as, V4_OP_JUMP); + v4_asm_resolve(&as, l2, v4_asm_label(&as)); + O(DROP); LIT(0); + v4_asm_resolve(&as, l3, v4_asm_label(&as)); + O(PUSH); + O(DUP); + l4 = v4_asm_branch_fwd(&as, V4_OP_MINUS_IF); + O(DROP); O(OVER); LIT(1); O(AND); + l5 = v4_asm_branch_fwd(&as, V4_OP_JUMP); + v4_asm_resolve(&as, l4, v4_asm_label(&as)); + O(DROP); LIT(0); + v4_asm_resolve(&as, l5, v4_asm_label(&as)); + O(RPOP); O(ADD); O(RPOP); O(ADD); O(PUSH); + O(AND); LIT(1); O(AND); O(PUSH_A); O(TWO_STAR); O(ADD); O(RPOP); + O(SEMI); + } /* : UM/MOD ( ulo uhi ud -- urem uquot ) section 4 * a! 31 FOR