std79 exerciser campaign: 27/27 legs clean, two real D. bugs found (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

Completed the FORTH-79 standard-dictionary cross-ISA exerciser campaign: all
9 identities (zuse, rajames/bob, 00-06) x all 3 architectures (amd64,
aarch64, riscv64), 27 legs total. No crashes, no heap corruption across the
full run -- real-world validation that the WIREBIND use-after-free fix
(commit 9142dda, FABRIC-3.md SXIII) holds under genuine multi-cycle load,
not just the synthetic repro used to verify it.

Cross-identity parity is perfect: 0 diffs across all 9 identities on each
architecture. Two real bugs found in double-precision (D.) output, reported
per this project's standing rule (report, don't fix without being asked):

- M* on a negative operand -> D. reports DOUBLE-OVERFLOW, universally
  across all three architectures (engine-level bug, not arch-specific).
- D+ on two negative doubles -> D. reports DOUBLE-OVERFLOW on aarch64
  only; amd64 and riscv64 both correctly print -2. cell_t width confirmed
  64-bit on all three (ruled out as the cause); exact mechanism still open.

Also fixed a test-harness timing issue (run_identities.sh): the retry
budget for USE-after-attach was too tight for boots with several live VMs
already accumulated, causing false "FAILED to USE" verdicts on identities
that actually succeeded a few seconds later. Widened the budget and
switched to smaller per-boot batches (2-3 identities) as the reliable
pattern for this shape of campaign.

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-10 23:13:13 -04:00
co-authored by Claude Sonnet 5
parent 9142dda2d6
commit cc81edf00a
44 changed files with 85536 additions and 1 deletions
+58
View File
@@ -2141,6 +2141,64 @@ 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 — reported, NOT fixed (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
`00``06` (WIREBIND hotplug attach → `USE` → feed the 24-case exerciser → detach), on amd64,
aarch64, and riscv64. Run as several smaller boots per architecture rather than one 9-cycle boot,
after observing real (non-bug) cumulative slowdown as more live VMs accumulate in a single long
boot -- past roughly the 3rd-4th WIREBIND cycle in one boot, per-tick MSG-TICK pump cost grows
enough that a fixed script retry budget starts racing the kernel and produces false "FAILED to
USE" verdicts on cycles that actually succeeded a few seconds later (confirmed live: the identity
in question showed `USE: now using NN` in the serial log immediately after the script had already
given up and issued its own premature `device_del`). Splitting into 2-3-identity boots avoided
this entirely and is the reliable pattern for any future campaign of this shape. (Separately
confirmed: no heap corruption, no crash, across the full run -- real-world validation that
§XIII's fix holds under genuine multi-cycle WIREBIND load, not just the synthetic 3-cycle repro
used to verify it.)
**Cross-identity parity: perfect.** All 9 identities produce byte-identical T01-T24 output on
each architecture (0 diffs, all three architectures). Confirms the FORTH-79 ACL-lockdown
personality (`acl-std79.4th`) and the standard dictionary itself behave identically regardless of
which identity is running them -- expected, and a useful negative result on its own.
**Two real bugs found, both in double-precision (`D.`) output, neither fixed here per this
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.
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
9 identities, 100% consistently. A genuine cross-ISA divergence, not a flaky/intermittent
result.
Investigated (not fixed): read `double_word_d_plus()` (`double_words.c`) and
`format_word_d_dot()` (`format_words.c`). `D.`'s check is `(dhigh == 0) || (dhigh == -1 &&
dlow < 0)` -- correct FORTH-79 double-overflow semantics for a properly sign-extended result.
`D+`'s carry-detection casts through `unsigned long` for the low-cell addition, which would be
a real per-architecture bug *if* `unsigned long`'s width differed from `cell_t`'s on any of
these targets -- checked and ruled out: `cell_t` is `int64_t` on amd64 and aarch64 explicitly
(`vm.h`'s `#if defined(__amd64__) || ... || defined(__aarch64__) || ...`), and `signed
long`/`unsigned long` (the `#else` branch, since `__riscv` isn't in that `#if`) on riscv64 --
confirmed 64-bit on all three empirically via `T08: -1 1 RSHIFT` printing the identical
19-digit `9223372036854775807` on every architecture, which only a genuine 64-bit arithmetic
shift can produce. So this is not a cell-width mismatch; the actual mechanism producing a wrong
`dhigh` specifically on aarch64, for this specific input, is still open.
**Next step, if picked up:** trace `D+`'s carry computation with a targeted probe on aarch64
specifically for the `-5 S>D 3 S>D D+` sequence, comparing the pushed `result_high` against the
expected `-1` right before `SWAP D.` runs -- narrows whether the defect is in `D+` itself, in
`S>D`'s sign-extension, or somewhere in the stack-manipulation path between them. `M*`'s
universal failure (bug 1) is a separate, likely unrelated defect and would need its own trace
starting from `M*`'s own double-cell multiply, not `D+`'s.
## XIII. Heap corruption under repeated WIREBIND attach/detach cycling — root-caused and CLOSED
2026-09-10