Fix D+/D-/DNEGATE: use ucell_t instead of unsigned long (FABRIC-3.md §XII.4)
double_word_d_plus(), double_word_d_minus(), and double_word_dnegate() (double_words.c) all cast through plain `unsigned long` for their carry/ borrow-detection arithmetic. On this aarch64 bare-metal cross-compile target, unsigned long is 32-bit (confirmed: sizeof(unsigned long)==4) -- amd64 and riscv64 both happen to have a 64-bit long, so the identical code only broke on aarch64. The low-cell arithmetic silently truncated to 32 bits, then widened back to cell_t via ordinary (non-sign-extending) conversion, producing a wrong result whenever the true 64-bit result was negative -- D. then correctly, faithfully reported DOUBLE-OVERFLOW on the resulting malformed double. vm.h already defines ucell_t for exactly this: same conditional as cell_t, guaranteed width-matched on every target. print_number_formatted() (format_words.c) already used it correctly; these three words didn't. Switched all three to ucell_t -- a one-word-class fix, no logic change. Verified: rebuilt and booted all three architectures clean. T19 (D+) on aarch64 now correctly prints -2, matching amd64/riscv64; T20 (DNEGATE) unaffected everywhere. Additional manual cases beyond the original exerciser, run live on aarch64 to specifically exercise the >32-bit-magnitude path the old bug depended on: D- (-5-3=-8), DNEGATE on 2^33 (8589934592 -> -8589934592), D+ crossing the same boundary (3+8589934592=8589934595) -- all correct. d_compare() (backing DMAX/DMIN/D</D=) has the identical latent pattern but is out of scope for this fix (not named in the request, never exercised by the campaign) -- left open, flagged in FABRIC-3.md. M*'s separate, universal-across-all-three-architectures DOUBLE-OVERFLOW bug is also untouched -- unrelated defect, not part of this fix. 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
91f7b39d3c
commit
bea8d7436a
+35
-17
@@ -2142,7 +2142,8 @@ 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 — reported, NOT fixed (2026-09-10, after §XIII's WIREBIND fix)
|
||||
FORTH-79 engine bugs found — bug 2 (`D+`/`D-`/`DNEGATE`) root-caused and FIXED 2026-09-11;
|
||||
bug 1 (`M*`) still open, report only (2026-09-10, 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
|
||||
@@ -2173,14 +2174,14 @@ project's standing rule (report, don't fix without being asked):**
|
||||
`D.`'s own overflow check, or both), not architecture-specific. Universal, 100% reproducible
|
||||
across all 9 identities × 3 architectures.
|
||||
|
||||
2. **`D+` on two negative doubles → `D.` reports `DOUBLE-OVERFLOW`, aarch64 only.**
|
||||
`T19: -5 S>D 3 S>D D+ SWAP D. CR` (computing -5 + 3 in double precision) correctly prints `-2`
|
||||
on amd64 and riscv64, for all 9 identities -- but prints `DOUBLE-OVERFLOW` on aarch64, for all
|
||||
2. **`D+` on two negative doubles → `D.` reports `DOUBLE-OVERFLOW`, aarch64 only — root-caused
|
||||
2026-09-11, FIXED 2026-09-11 (`D+`/`D-`/`DNEGATE`).**
|
||||
`T19: -5 S>D 3 S>D D+ SWAP D. CR` (computing -5 + 3 in double precision) correctly printed `-2`
|
||||
on amd64 and riscv64, for all 9 identities -- but printed `DOUBLE-OVERFLOW` on aarch64, for all
|
||||
9 identities, 100% consistently. A genuine cross-ISA divergence, not a flaky/intermittent
|
||||
result.
|
||||
|
||||
**Root-caused 2026-09-11 (still not fixed -- report only, per this project's standing
|
||||
rule).** First pass (2026-09-10) checked `cell_t`'s width and found it 64-bit on all three
|
||||
**Root-caused 2026-09-11.** First pass (2026-09-10) checked `cell_t`'s width and found it 64-bit on all three
|
||||
architectures (`T08: -1 1 RSHIFT` prints the identical 19-digit `9223372036854775807`
|
||||
everywhere), and wrongly concluded from that alone that `D+`'s `unsigned long` carry-detection
|
||||
cast couldn't be a width-mismatch bug -- **that conclusion was wrong**: `cell_t`'s width and
|
||||
@@ -2213,18 +2214,35 @@ project's standing rule (report, don't fix without being asked):**
|
||||
correctly. `D+`'s carry-detection using bare `unsigned long` instead is the actual defect --
|
||||
a one-word class of bug (`unsigned long` should be `ucell_t`), not a logic error.
|
||||
|
||||
**Wider scope, not yet empirically exercised:** `double_word_d_minus()` (`D-`),
|
||||
`double_word_dnegate()` (`DNEGATE`), and the `d_compare()` helper (used by `DMAX`/`DMIN`/
|
||||
`D<`/`D=`) all use the identical bare-`unsigned long` pattern (`double_words.c` lines
|
||||
~111-113, ~136, ~170-171) and are equally vulnerable in principle on any platform where
|
||||
**Fixed 2026-09-11, on explicit request ("fix the D+/D-/DNEGATE bugs using ucell_t"):**
|
||||
`double_word_d_plus()`, `double_word_d_minus()` (`D-`), and `double_word_dnegate()`
|
||||
(`DNEGATE`) all shared the identical bare-`unsigned long` pattern (`double_words.c` lines
|
||||
~83-85, ~111-113, ~136) and were all equally vulnerable in principle on any platform where
|
||||
`long` is narrower than `cell_t`. `DNEGATE` was exercised by the campaign (`T20: -5 S>D
|
||||
DNEGATE SWAP D. CR`) and happened to print the correct `5` on all three architectures --
|
||||
traced why: for this specific small input, the 32-bit-truncated magnitude computation still
|
||||
produces the mathematically correct positive result even without proper 64-bit sign
|
||||
extension, so the bug is latent, not absent, for `DNEGATE` here. `D-`/`DMAX`/`DMIN`/`D</D=`
|
||||
were never exercised by `std79-exerciser.fth` at all. `double_word_d_two_star()`/
|
||||
`double_word_d_two_slash()` (`D2*`/`D2/`) use `unsigned long long` instead, which the C
|
||||
standard guarantees is at least 64-bit everywhere -- not part of this bug class.
|
||||
DNEGATE SWAP D. CR`) and happened to print the correct `5` on all three architectures for
|
||||
that specific small input -- traced why: the 32-bit-truncated magnitude computation still
|
||||
produces the mathematically correct positive result without proper 64-bit sign extension for
|
||||
a value that small, so the bug was latent, not absent, there. `D-` was never exercised by
|
||||
`std79-exerciser.fth` at all. All three now use `ucell_t` in place of `unsigned long`, same
|
||||
pattern as `print_number_formatted()`'s existing correct usage.
|
||||
|
||||
**Verified:** rebuilt and booted all three architectures (amd64, aarch64, riscv64) clean.
|
||||
`T19` on aarch64 now correctly prints `-2` (matching amd64/riscv64), `T20` (`DNEGATE`)
|
||||
unaffected on all three. Additional manual cases beyond the original exerciser, run live on
|
||||
aarch64 to specifically exercise the >32-bit-magnitude path the old bug depended on:
|
||||
`-5 S>D 3 S>D D- SWAP D.` → `-8` (correct); `8589934592 S>D DNEGATE SWAP D.` → `-8589934592`
|
||||
(correct, `8589934592` = 2^33, well past the 32-bit boundary the bug truncated at);
|
||||
`3 S>D 8589934592 S>D D+ SWAP D.` → `8589934595` (correct, same boundary-crossing check for
|
||||
`D+`). `M*`'s universal failure (bug 1, below) is untouched -- unrelated defect, `D+`/`D-`/
|
||||
`DNEGATE`-specific fix only.
|
||||
|
||||
**Still open, not fixed (out of the scope actually requested):** the `d_compare()` helper
|
||||
(used by `DMAX`/`DMIN`/`D<`/`D=`) uses the same bare-`unsigned long` pattern
|
||||
(`double_words.c` lines ~170-171) and is equally vulnerable in principle -- never exercised by
|
||||
`std79-exerciser.fth`, not fixed here since the fix request named `D+`/`D-`/`DNEGATE`
|
||||
specifically. `double_word_d_two_star()`/`double_word_d_two_slash()` (`D2*`/`D2/`) use
|
||||
`unsigned long long` instead, which the C standard guarantees is at least 64-bit everywhere --
|
||||
not part of this bug class, nothing to fix there.
|
||||
|
||||
`M*`'s universal failure (bug 1) is a separate, unrelated defect (present on every architecture,
|
||||
so not a `long`-width issue) and would need its own trace starting from `M*`'s own double-cell
|
||||
|
||||
Reference in New Issue
Block a user