You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
gft_sadd's Zig backend traps on the negative shift in its discarded magsub call (verify_multitarget: CROSS-TARGET MISMATCH) #5665
Found while running emit-bitexact on PR #5662 — first time the gate woke in a while, and the failure is not that PR's spec.
The measurement
tools/verify_multitarget.py --require ("GF-T primitives agree across C + Rust + Zig + model"):
OK gft_smul.smul: C == Python model BIT-EXACT over 1744 operand pairs
OK gft_smul.smul: Rust == Python model BIT-EXACT
OK gft_smul.smul: Zig == Python model BIT-EXACT
OK gft_sadd.sadd: C == Python model BIT-EXACT
OK gft_sadd.sadd: Rust == Python model BIT-EXACT
Zig target run: exited -6 (signal 6)
/tmp/…/m.zig:125:20: in magsub (m)
FAIL gft_sadd.sadd: Zig backend failed to build/run
CROSS-TARGET MISMATCH
The same gate was red on master on 2026-08-20 and has not run since 2026-08-28, so this is standing debt the sleeping gate was carrying.
The root cause
specs/ternary/gft_sadd.t27 — same pattern as gft_signed_mac.t27 and gft_signed_dot4.t27:
var r : i32 = magsub(ma, mb);
if (ma < mb) { r = magsub(mb, ma); bsign = sb; }
The first magsub(ma, mb) is discarded whenever ma < mb, but it still executes — and in that direction d = ho − lo_o is negative, so ls >> d shifts by a negative amount. The C and Rust backends happen to tolerate it (garbage computed, then overwritten); the Zig backend traps (signal 6, safety panic on the negative shift). The Verilog path lowers both calls unconditionally too — which is exactly why the r == 0 guard was moved to the top level in W836 — but simulators don't trap on it.
The fix shape
Reorder so the discarded call never runs with the smaller magnitude first:
var bsign : i32 = sa;
var r : i32 = 0;
if (ma < mb) { r = magsub(mb, ma); bsign = sb; }
else { r = magsub(ma, mb); }
Same behaviour on every backend (the branch already existed), no negative shift anywhere, and gft_signed_mac.t27 / gft_signed_dot4.t27 carry the identical pattern and will trap the same way the day the multitarget harness widens its primitive list. A transliteration of the reference semantics over 20000 random operands shows the swap is behaviour-preserving (zero divergences) — the value of the first call was never read when ma < mb.
Not fixed here: this is a cross-backend contract question (should the Zig backend trap on UB-shaped t27 code at all, or should sadd simply never emit it), and it belongs with whoever owns the backend-parity rule.
Resolved by 34849f5 (fix(gft): sadd orders before it subtracts, underflow is zero — #5737): every sadd in the corpus now orders its magnitudes BEFORE calling magsub, in the if (ma < mb) { r = magsub(mb, ma); ... } else { r = magsub(ma, mb); } shape, so no discarded call ever executes with a negative exponent. Verified from the bench just now, on current master:
OK gft_smul.smul: C == Python model BIT-EXACT over 1744 operand pairs
OK gft_smul.smul: Rust == Python model BIT-EXACT over 1744 operand pairs
OK gft_smul.smul: Zig == Python model BIT-EXACT over 1744 operand pairs
OK gft_sadd.sadd: C == Python model BIT-EXACT over 1744 operand pairs
OK gft_sadd.sadd: Rust == Python model BIT-EXACT over 1744 operand pairs
OK gft_sadd.sadd: Zig == Python model BIT-EXACT over 1744 operand pairs
ALL TARGETS BIT-EXACT (Verilog[via emit gate] + C + Rust + Zig + model agree)
python3 tools/verify_multitarget.py --require — the exact check that read Zig target run: exited -6 (signal 6) in m.zig:125 magsub on this spec — is green on the fixed corpus. The emit-bitexact workflow has not run on master since the fix landed (its last runs predate it), so this local run on master 1b12580 is the verification of record until the next scheduled pass.
Activity
dmitrii-f-t27 commentedon Oct 3, 2026
Resolved by 34849f5 (fix(gft): sadd orders before it subtracts, underflow is zero — #5737): every
saddin the corpus now orders its magnitudes BEFORE callingmagsub, in theif (ma < mb) { r = magsub(mb, ma); ... } else { r = magsub(ma, mb); }shape, so no discarded call ever executes with a negative exponent. Verified from the bench just now, on current master:python3 tools/verify_multitarget.py --require— the exact check that readZig target run: exited -6 (signal 6)in m.zig:125 magsub on this spec — is green on the fixed corpus. The emit-bitexact workflow has not run on master since the fix landed (its last runs predate it), so this local run on master 1b12580 is the verification of record until the next scheduled pass.— dmitrii-f-t27