Fix D+/D-/DNEGATE: 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

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:
Robert Allan James
2026-09-11 07:37:57 -04:00
co-authored by Claude Sonnet 5
parent 91f7b39d3c
commit bea8d7436a
10 changed files with 27227 additions and 28 deletions
+15 -10
View File
@@ -79,10 +79,13 @@ void double_word_d_plus(VM *vm) {
cell_t d1high = vm_pop(vm);
cell_t d1low = vm_pop(vm);
// Use unsigned arithmetic for carry detection
unsigned long ud1low = (unsigned long) d1low;
unsigned long ud2low = (unsigned long) d2low;
unsigned long uresult_low = ud1low + ud2low;
// Use unsigned arithmetic for carry detection. ucell_t (vm.h), not plain
// unsigned long -- unsigned long is only 32-bit on some bare-metal
// aarch64 cross-compile targets, silently truncating this addition and
// losing the sign-extension D. depends on (FABRIC-3.md §XII.4).
ucell_t ud1low = (ucell_t) d1low;
ucell_t ud2low = (ucell_t) d2low;
ucell_t uresult_low = ud1low + ud2low;
cell_t result_low = (cell_t) uresult_low;
cell_t carry = (uresult_low < ud1low) ? 1 : 0;
@@ -107,10 +110,11 @@ void double_word_d_minus(VM *vm) {
cell_t d1high = vm_pop(vm);
cell_t d1low = vm_pop(vm);
// Use unsigned arithmetic for borrow detection
unsigned long ud1low = (unsigned long) d1low;
unsigned long ud2low = (unsigned long) d2low;
unsigned long uresult_low = ud1low - ud2low;
// Use unsigned arithmetic for borrow detection. ucell_t, not plain
// unsigned long -- see double_word_d_plus()'s matching comment.
ucell_t ud1low = (ucell_t) d1low;
ucell_t ud2low = (ucell_t) d2low;
ucell_t uresult_low = ud1low - ud2low;
cell_t result_low = (cell_t) uresult_low;
cell_t borrow = (ud1low < ud2low) ? 1 : 0;
@@ -132,8 +136,9 @@ void double_word_dnegate(VM *vm) {
cell_t dhigh = vm_pop(vm);
cell_t dlow = vm_pop(vm);
// Negate: ~d + 1
unsigned long new_low = ~(unsigned long) dlow + 1;
// Negate: ~d + 1. ucell_t, not plain unsigned long -- see
// double_word_d_plus()'s matching comment.
ucell_t new_low = ~(ucell_t) dlow + 1;
cell_t new_dhigh = ~dhigh + (new_low == 0 ? 1 : 0);
vm_push(vm, (cell_t) new_low);