feat(v4.0.0): UM* corrections after the loop; return headroom 4 -> 6
UM* parked its three corrections (c_hi, c_lo, t0) on the return stack before the +* loop, leaving its caller 4 return entries. Now u1 and u2 wait on the data stack under the loop -- +* touches only T, S and A -- and the corrections are made after it, with -if in place of 0< calls. The return stack holds the loop count, then at most two temporaries. The loop count is pushed before s is made, so the data stack never holds more than four cells: data headroom is unchanged at 6. Headroom (data cells under args / return entries): 6/4 -> 6/6. Q.*, which calls UM* four times, goes from 2/1 to 2/2. Same checks as before at 32- and 64-bit cells, optimised and ASan+UBSan; breaking any of the four corrections fails 4900+ checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ab3b708540
commit
54e1cdafb1
@@ -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. |
|
||||
|
||||
+51
-25
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user