Fix M/MOD: hand-rolled 128/64 bit-serial division (FABRIC-3.md §XII.4)
mixed_math_word_m_slash_mod() had the identical bug class already fixed
in M* (commit 9a09949): it reconstructed the dividend from
`(dhigh << 32) | (dlow & 0xFFFFFFFF)`, the same wrong "32-bit halves of a
64-bit value" assumption. Latent for small inputs (fits in 32 bits, so the
reconstruction coincidentally worked), confirmed genuinely broken for a
true wide double -- feeding M*'s own correct 10^24 output into it gave a
quotient/remainder wrong by many orders of magnitude and the wrong sign.
Unlike M*, this one can't just switch to __int128 -- __int128's own `/`/`%`
need libgcc's __udivti3/__umodti3 for 128-bit division, unavailable in
this freestanding, -nostdlib build (the exact constraint
src/starkernel/arch/amd64/timer.c's own doc comment already flagged:
__int128 multiply/shift-by-constant/compare/subtract all compile clean,
only division doesn't). Fixed instead with a hand-rolled unsigned
128-by-64-bit bit-serial (restoring) long division -- 128 iterations of
shift-by-1/compare/subtract only, all in the safe set. Operates on
magnitudes via unsigned negation from 0 (well-defined even for the
extreme negative edge); signs reapplied afterward matching the same C99
truncating-toward-zero convention the previous, narrower implementation
already used, unchanged.
Verified: rebuilt all three architectures, confirmed clean link with no
__udivti3/__umodti3 undefined-symbol errors. Booted and tested all three,
identical results: 1000000000000 1000000000000 M* SWAP 1000000 M/MOD ->
1000000000000000000 remainder 0 (exact division, the case that was wrong
by orders of magnitude before); three sign-combination cases all correct
(-+, +-, --), confirming sign handling survived the magnitude-only
rewrite. T15 (original small-input case) unaffected. Full 24-case
exerciser reran clean on all three, no regressions.
All bugs found by the std79 exerciser campaign, including this one found
while fixing another, are now closed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXieurDfDSsDFdnSyusuWo
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
9a09949c69
commit
70db955ac9
+37
-14
@@ -2141,10 +2141,9 @@ produced one interesting, unconfirmed data point before the first panic: `-123 4
|
||||
against the other two architectures once the campaign completes; not yet root-caused or reported
|
||||
as a bug on its own.
|
||||
|
||||
### XII.4 — Campaign completed: full 27-leg run (9 identities × 3 architectures), two real
|
||||
FORTH-79 engine bugs found — BOTH root-caused and FIXED 2026-09-11 (bug 1 `M*`; bug 2 `D+`/`D-`/
|
||||
`DNEGATE`/`d_compare`); a third, `M/MOD`, found while fixing bug 1, reported not fixed
|
||||
(2026-09-10 campaign, after §XIII's WIREBIND fix)
|
||||
### XII.4 — Campaign completed: full 27-leg run (9 identities × 3 architectures) — ALL bugs
|
||||
found (`M*`, `D+`/`D-`/`DNEGATE`/`d_compare`, and `M/MOD` found along the way) root-caused and
|
||||
FIXED, 2026-09-11 (2026-09-10 campaign, after §XIII's WIREBIND fix)
|
||||
|
||||
All 27 legs run: `zuse` (auto-attached, exercised directly on Hera's own console) plus `rajames`
|
||||
(the `bob` thumbdrive's actual registered identity -- see the naming-mismatch note below) and
|
||||
@@ -2203,16 +2202,40 @@ project's standing rule (report, don't fix without being asked):**
|
||||
proof the multiply itself is now genuinely 128-bit, not just sign-extension-correct for small
|
||||
values). Full 24-case exerciser reran clean on all three, no regressions.
|
||||
|
||||
**Found while verifying, NOT fixed (report only, out of scope of what was requested):**
|
||||
`mixed_math_word_m_slash_mod()` (`M/MOD`, the very next function in the same file) has the
|
||||
*identical* bug class -- it reconstructs the dividend from `(dhigh << 32) | (dlow &
|
||||
0xFFFFFFFF)`, the same wrong "32-bit halves of a 64-bit value" assumption `M*` had. Latent for
|
||||
small inputs (`T15: 100000 S>D SWAP 7 M/MOD . .` -> `14285 5`, correct, because `100000` fits
|
||||
entirely within 32 bits so the reconstruction coincidentally works), but confirmed genuinely
|
||||
broken for a true wide double: feeding the now-fixed `M*`'s own correct large-magnitude output
|
||||
into `M/MOD` (`1000000000000 1000000000000 M* 1000000 M/MOD . .`, expected quotient
|
||||
`10^18`) produces `-6845471433603` remainder, `-99710` quotient -- wrong by many orders of
|
||||
magnitude and the wrong sign. Not touched here; flag for a future fix request.
|
||||
**`M/MOD` found broken by the same bug class, then FIXED 2026-09-11** (on explicit follow-up
|
||||
request "fix it"): `mixed_math_word_m_slash_mod()` (the very next function in the same file)
|
||||
reconstructed the dividend from `(dhigh << 32) | (dlow & 0xFFFFFFFF)`, the same wrong "32-bit
|
||||
halves of a 64-bit value" assumption `M*` had. Latent for small inputs (`T15: 100000 S>D SWAP
|
||||
7 M/MOD . .` -> `14285 5`, correct, because `100000` fits entirely within 32 bits so the
|
||||
reconstruction coincidentally worked), confirmed genuinely broken for a true wide double
|
||||
(feeding the fixed `M*`'s own `10^24` output into it gave a quotient wrong by many orders of
|
||||
magnitude and the wrong sign).
|
||||
|
||||
Fixing this one is harder than `M*`: `__int128`'s own `/`/`%` operators need libgcc's
|
||||
`__udivti3`/`__umodti3` for 128÷64 division, unavailable in this freestanding, `-nostdlib`
|
||||
build (confirmed at link time -- exactly the constraint `timer.c`'s own doc comment already
|
||||
flagged: `__int128` multiply/shift-by-constant/compare/subtract all compile clean, only
|
||||
division doesn't). Fixed instead with a hand-rolled unsigned 128-bit-by-64-bit bit-serial
|
||||
(restoring) long division -- 128 iterations of shift-by-1/compare/subtract only, all in the
|
||||
safe set. Operates on magnitudes (via unsigned negation from `0`, well-defined even for the
|
||||
extreme negative edge, unlike signed negation); signs reapplied afterward matching C99
|
||||
truncating-toward-zero division (quotient sign = XOR of operand signs, remainder sign = the
|
||||
dividend's own sign) -- the exact convention the previous, narrower implementation already
|
||||
used via plain C `/`/`%`, unchanged here.
|
||||
|
||||
**Verified:** rebuilt all three architectures -- confirmed clean link with no
|
||||
`__udivti3`/`__umodti3` undefined-symbol errors (would have failed exactly the way `timer.c`'s
|
||||
own history warned about, had `__int128` division been used instead). Booted and tested all
|
||||
three, identical results everywhere: `1000000000000 1000000000000 M* SWAP 1000000 M/MOD` ->
|
||||
quotient `1000000000000000000` (10^18), remainder `0` (exact division, the case that was
|
||||
wrong by many orders of magnitude before); three sign-combination cases
|
||||
(`-100000 S>D SWAP 7 M/MOD` -> `-14285 -5`; `100000 S>D SWAP -7 M/MOD` -> `-14285 5`;
|
||||
`-100000 S>D SWAP -7 M/MOD` -> `14285 -5`) all correct, confirming the sign-handling survived
|
||||
the magnitude-only rewrite. `T15` (the original small-input case) unaffected. Full 24-case
|
||||
exerciser reran clean on all three, no regressions.
|
||||
|
||||
**All bugs found by this campaign, including the one found while fixing another, are now
|
||||
closed.**
|
||||
|
||||
2. **`D+` on two negative doubles → `D.` reports `DOUBLE-OVERFLOW`, aarch64 only — root-caused
|
||||
2026-09-11, FIXED 2026-09-11 (`D+`/`D-`/`DNEGATE`).**
|
||||
|
||||
Reference in New Issue
Block a user