Fix d_compare: use ucell_t instead of unsigned long (FABRIC-3.md §XII.4)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

d_compare() (double_words.c), the static helper backing DMAX/DMIN/D</D=,
had the same bare-unsigned-long pattern already fixed in D+/D-/DNEGATE
(commit bea8d74) -- 32-bit unsigned long on this aarch64 target silently
truncating the low-cell comparison. Switched to ucell_t.

Verified with four cases designed specifically to expose the old
truncation: two doubles sharing the same high cell but with low cells
differing only in bits 32-63 (invisible to a 32-bit-truncated compare,
e.g. 2^32 vs 0):
  4294967296 0 0 0 D= .            -> 0  (correctly not equal)
  4294967296 0 0 0 DMIN SWAP D.    -> 0  (correctly picks the smaller)
  0 0 4294967296 0 D< .            -> -1 (correct)
  4294967296 0 0 0 D< .            -> 0  (correct)
All four pass identically on amd64, aarch64, and riscv64. These cases
were never run before this fix -- the exerciser campaign never touched
DMAX/DMIN/D</D= at all, so this is the first real evidence d_compare()
was ever exercised on any architecture. Full 24-case exerciser also
reran clean on all three architectures (no regression).

D2*/D2/ use unsigned long long (C-standard-guaranteed >=64-bit) and were
never part of this bug class. All four affected words (D+, D-, DNEGATE,
d_compare) are now fixed; M*'s separate universal DOUBLE-OVERFLOW bug
remains open (unrelated defect, out of scope here).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXieurDfDSsDFdnSyusuWo
This commit is contained in:
Robert Allan James
2026-09-11 07:44:14 -04:00
co-authored by Claude Sonnet 5
parent bea8d7436a
commit 1a716c8048
10 changed files with 27082 additions and 11 deletions
+21 -8
View File
@@ -2142,8 +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 — 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)
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)
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
@@ -2236,13 +2236,26 @@ project's standing rule (report, don't fix without being asked):**
`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
**`d_compare()` fixed too, 2026-09-11** (on explicit follow-up request "fix d_compare too"):
the helper backing `DMAX`/`DMIN`/`D<`/`D=` (`double_words.c` lines ~170-171) had the identical
bare-`unsigned long` pattern, never exercised by `std79-exerciser.fth` itself. Switched to
`ucell_t`, same as the other three. Verified with four cases designed specifically to expose
the old 32-bit-truncation bug -- two doubles sharing the same high cell but with low cells
differing only in bits 32-63 (invisible to a 32-bit-truncated compare, e.g. `2^32` vs `0`):
`4294967296 0 0 0 D= .``0` (correctly *not* equal; the old bug would truncate both low
cells to `0` and wrongly report equal); `4294967296 0 0 0 DMIN SWAP D.``0` (correctly
picks the smaller double's low cell; the old bug's false "equal" tie-break would have
returned the wrong operand for this ordering); `0 0 4294967296 0 D< .``-1` and
`4294967296 0 0 0 D< .``0` (both directions of the same comparison, both correct). All
four pass identically on amd64, aarch64, and riscv64 -- confirms the fix and, since these
cases were never run before this fix, is the first real evidence `d_compare()` was ever
correct or incorrect on any architecture (the campaign itself never touched `DMAX`/`DMIN`/
`D<`/`D=` 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, nothing to fix there.
were never part of this bug class, nothing to fix there.
**All four affected words (`D+`, `D-`, `DNEGATE`, `d_compare`) are now closed.** The only
remaining open item from this campaign is bug 1 (`M*`, below) -- a separate, unrelated defect.
`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