Fix M*: use __int128 for a genuine 128-bit double-cell product (FABRIC-3.md §XII.4)
mixed_math_word_m_star() confused "double" (two full cell_t-width cells,
128 bits total on this 64-bit build -- what D+/D-/D./etc. all actually
expect) with "the low/high 32-bit halves of a single 64-bit product" --
code clearly written assuming cell_t is 32-bit. It computed an ordinary
64-bit `long long` product (already wrong for any true product exceeding
64 bits, since long long is the same width as one cell here) and split
that into 32-bit halves via `result & 0xFFFFFFFF` / `result >> 32`. For a
small negative product like -56088, this produced a positive, zero-
extended low cell paired with a correctly-looking dhigh=-1 -- D.'s
overflow check (correctly) rejected the resulting malformed double, on
every architecture, every time (this bug was never architecture-specific,
unlike the D+/D-/DNEGATE/d_compare family already fixed in bea8d74/
1a716c8).
Fixed via __int128 for a genuine 64x64->128-bit multiply, no truncation --
an already-established safe pattern in this codebase for exactly this
operation (src/starkernel/crypto/fe25519.c/scalar25519.c already use it;
src/starkernel/arch/amd64/timer.c's own doc comment confirms __int128
multiply/shift-by-constant compile cleanly with zero undefined symbols on
all three target toolchains -- only division needs unavailable libgcc
support, not used here).
Verified: rebuilt and booted all three architectures clean. T13/T14 both
correct everywhere (56088/-56088). Manual cases confirm genuine 128-bit
precision: -1 -1 M* -> 1; 1000000000000 1000000000000 M* -> dhigh=54210
dlow=2003764205206896640 (10^12 x 10^12 = 10^24, correctly exceeding 64
bits). Full 24-case exerciser reran clean on all three, no regressions.
Found while verifying, NOT fixed (report only, out of scope of this
request): mixed_math_word_m_slash_mod() (M/MOD, the very next function in
the same file) has the identical bug class -- confirmed genuinely broken
for a true wide double (feeding this fix's own correct large-magnitude M*
output into M/MOD produces a result wrong by many orders of magnitude and
the wrong sign). Flagged in FABRIC-3.md for a future fix request.
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
1a716c8048
commit
9a09949c69
+46
-6
@@ -2142,8 +2142,9 @@ against the other two architectures once the campaign completes; not yet root-ca
|
||||
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 — bug 2 (`D+`/`D-`/`DNEGATE`/`d_compare`) root-caused and FIXED
|
||||
2026-09-11; bug 1 (`M*`) still open, report only (2026-09-10, after §XIII's WIREBIND fix)
|
||||
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)
|
||||
|
||||
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
|
||||
@@ -2169,10 +2170,49 @@ which identity is running them -- expected, and a useful negative result on its
|
||||
project's standing rule (report, don't fix without being asked):**
|
||||
|
||||
1. **`M*` on a negative operand → `D.` reports `DOUBLE-OVERFLOW`, on all three architectures
|
||||
identically.** `T14: -123 456 M* SWAP D. CR` should print `-56088`; it prints `DOUBLE-OVERFLOW`
|
||||
on amd64, aarch64, *and* riscv64 -- an engine-level bug (in `M*`'s double-cell result, or in
|
||||
`D.`'s own overflow check, or both), not architecture-specific. Universal, 100% reproducible
|
||||
across all 9 identities × 3 architectures.
|
||||
identically — root-caused and FIXED 2026-09-11.** `T14: -123 456 M* SWAP D. CR` should print
|
||||
`-56088`; it printed `DOUBLE-OVERFLOW` on amd64, aarch64, *and* riscv64 -- an engine-level bug,
|
||||
universal, 100% reproducible across all 9 identities × 3 architectures (unlike bug 2 below,
|
||||
this one really is architecture-independent, since it doesn't involve `long`'s width at all).
|
||||
|
||||
**Root cause:** `mixed_math_word_m_star()` (`mixed_arithmetic_words.c`) confused "double" (two
|
||||
full `cell_t`-width cells, 128 bits total on this 64-bit build -- what `D+`/`D-`/`D.`/etc. all
|
||||
actually expect) with "the low/high 32-bit halves of a single 64-bit product" -- code clearly
|
||||
written assuming `cell_t` is 32-bit. It computed an ordinary 64-bit `long long` product
|
||||
(`(long long) n1 * (long long) n2`, already wrong/truncated for any true product not fitting
|
||||
in 64 bits, since `long long` is only 64-bit here, the same width as one cell already) and
|
||||
then split *that* into 32-bit low/high halves via `result & 0xFFFFFFFF` / `result >> 32`. For
|
||||
a small negative product like `-56088` (fits entirely in one 64-bit cell with sign extension),
|
||||
this produced a *positive*, zero-extended low cell paired with a correctly-looking `dhigh=-1`
|
||||
-- the exact same "malformed double, `D.` correctly rejects it" shape as bug 2's `D+` defect,
|
||||
just from an entirely different, unrelated piece of code.
|
||||
|
||||
**Fix:** switched to `__int128` for a genuine 64×64→128-bit multiply (no truncation), an
|
||||
already-established safe pattern in this codebase for exactly this operation --
|
||||
`src/starkernel/crypto/fe25519.c`/`scalar25519.c` already use it, and
|
||||
`src/starkernel/arch/amd64/timer.c`'s own doc comment confirms `__int128`
|
||||
multiply/shift-by-constant compile cleanly with zero undefined symbols on all three target
|
||||
toolchains (only *division* needs libgcc support unavailable in this freestanding build --
|
||||
not needed for `M*`). `dlow` = low 64 bits of the 128-bit product, `dhigh` = high 64 bits
|
||||
(arithmetic shift, sign-preserving).
|
||||
|
||||
**Verified:** rebuilt and booted all three architectures clean. `T13`/`T14` both correct
|
||||
everywhere now (`56088`/`-56088`). Additional manual cases run live on all three, identical
|
||||
results: `-1 -1 M*` → `1` (small negative×negative); `1000000000000 1000000000000 M*` →
|
||||
`dhigh=54210 dlow=2003764205206896640` (10^12 × 10^12 = 10^24, correctly exceeding 64 bits --
|
||||
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.
|
||||
|
||||
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