feat(v4.0.0): call-free UM/MOD loop for D-2 stack headroom
The first UM/MOD was exact but called 0<, U<, SWAP and OR inside its loop, so it left its caller only 3 return-stack entries. SM/REM pushes two signs before calling it, so under /MOD, M/MOD or */MOD the caller's return address would be silently overwritten (D-2 circular stacks). The loop now makes no calls. It branches on hi's top bit with -if, does the unsigned hi' >= d test as U< does but with in-line sign tests, subtracts with `inv a + inv`, and sets the quotient bit with `1 +` on an even lo'. The final SWAP is in line. Measured on the golden model at 32- and 64-bit cells: headroom data 3 -> 6 cells under args, return 3 -> 6 entries speed ~1355-1605 -> ~227-313 instruction words per call Still exact for every uhi < ud (edge-vector triples and 20000 random cases, optimised and ASan+UBSan). Retargeting each of the four in-loop branches to the wrong label fails more than 12000 checks each. DECOMPOSITION.md: section 4 UM/MOD replaced, with its derivation and stack limits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
41f59afeb3
commit
3b9d1e4337
@@ -221,23 +221,39 @@ dependency order.
|
||||
2* a 0< NEGATE + pop + ; \ lo' hi'
|
||||
|
||||
\ ---- 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
|
||||
\ set or hi' >= d, subtracts d from hi' and sets the new low bit of lo. The
|
||||
\ unsigned compare is done by sign tests in line (as U< does), and x - d is
|
||||
\ `inv a + inv`, so the loop body makes no calls: it needs one return-stack
|
||||
\ entry beyond its own count.
|
||||
: UM/MOD ( ulo uhi ud -- urem uquot )
|
||||
a!
|
||||
31 FOR
|
||||
over 0< NEGATE push \ R: lo's top bit (1/0)
|
||||
dup 0< push \ R: hi's top bit (-1/0)
|
||||
2* pop pop SWAP push OR pop \ lo hi' t hi shifted, lo bit brought in
|
||||
push SWAP 2* SWAP pop \ lo' hi' t
|
||||
over a U< 0= OR \ lo' hi' flag t OR hi' >= d
|
||||
IF a - SWAP 1 OR SWAP THEN
|
||||
a! 31 FOR
|
||||
-if L0 \ hi's top bit set:
|
||||
2* over -if L1 drop 1 + jump L2 \ hi' = 2hi + top bit of lo
|
||||
L1: drop L2: push 2* pop \ lo' = 2lo
|
||||
jump SUB \ true hi' >= 2^n > d
|
||||
L0:
|
||||
2* over -if L3 drop 1 + jump L4
|
||||
L3: drop L4: push 2* pop
|
||||
dup a xor -if L5 \ top bits of hi' and d differ:
|
||||
drop -if NOSUB jump SUB \ hi' >= d iff hi' has it
|
||||
L5: drop dup inv a + inv -if L6 \ same: hi' >= d iff hi'-d >= 0
|
||||
drop jump NOSUB
|
||||
L6: push drop pop jump SETBIT
|
||||
SUB: inv a + inv \ hi' - d
|
||||
SETBIT: push 1 + pop \ lo' is even: + 1 sets bit 0
|
||||
NOSUB:
|
||||
NEXT
|
||||
SWAP ;
|
||||
\ Executed on the golden model unchanged (2026-10-02): exact, q*d + r = uhi:ulo
|
||||
\ with r < d, for every uhi < ud at 32- and 64-bit cells. Outside that range
|
||||
over push push drop pop pop ; \ SWAP in line
|
||||
\ Executed on the golden model (2026-10-02): exact, q*d + r = uhi:ulo with
|
||||
\ r < d, for every uhi < ud at 32- and 64-bit cells. Outside that range
|
||||
\ (uhi >= ud, including ud = 0) the result is unspecified, as in FORTH-79; it
|
||||
\ always terminates after 32 steps. Stack use (D-2): at most 3 data cells may
|
||||
\ lie under the three arguments and at most 3 return-stack entries under its
|
||||
\ return address. For UM* the same limits are 6 and 4.
|
||||
\ 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
|
||||
\ 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.
|
||||
|
||||
\ ---- signed division, truncating toward zero (v3 semantics) -------------
|
||||
: SM/REM ( d n -- rem quot )
|
||||
|
||||
Reference in New Issue
Block a user