64-bit Multiply-Overflow
Big Numbers
Six orbiters trace looping paths on a Super Nintendo canvas. Each carries a 64-bit
momentum scaled by a growing factor; when the product overflows a 64-bit integer the
orbiter teleports to the mirror quadrant and flashes orange. This is compiler stress-test
#101 — the first demo to make llvm-mos
form an s64 multiply-high / multiply-overflow (G_UMULH/G_UMULO),
a lowering path no prior demo exercised.
Self-running — trails accumulate and sparks fire when a 64-bit product overflows.
Click the screen to focus, then watch. Tab away and it pauses. (The title holds a few seconds while the 64-bit gate computes — it is the heaviest compute in the battery.)
What it is
__builtin_mul_overflow(a, b, &result) stores the truncated product in
result and returns true if the true mathematical product didn't
fit the type. Deciding that for a 64-bit multiply requires the top 64 bits of the full
128-bit product — the multiply-high. On a chip with no hardware multiply and a
multiply legalizer capped at 32 bits, the compiler must build that 128-bit product from
32-bit partial products by hand. This demo drives both the unsigned and signed 64-bit
overflow checks.
// unsigned 64-bit multiply-overflow (G_UMULO at s64)
uint64_t up;
bool uov = __builtin_mul_overflow(ua, ub, &up);
// signed 64-bit multiply-overflow (G_SMULO at s64)
int64_t sp;
bool sov = __builtin_mul_overflow(sa, sb, &sp);
// llvm-mos lowers each to the FULL 128-bit product, whose high 64
// bits need G_UMULH/G_SMULH at s64 — composed from 32-bit __mulsi3
// pieces + __muldi3 glue, WITHOUT widening to 128 bits.
if (uov) { teleport_to_mirror(); emit_spark(); } Each orbiter's 64-bit momentum is multiplied by a growing scale every frame; the moment it overflows, the orbiter teleports to the mirror quadrant (127−x, 127−y), reverses direction, resets its momentum and emits an orange spark. Because each orbiter starts from a different seed, they overflow at staggered times, scattering sparks across the accumulating trails.
Compiler stress-test #101 — Round 6: hardening the fixes
| Item | What it exercises |
|---|---|
| G_UMULH / G_SMULH at s64 | The high half of a 64×64 → 128-bit product. To decide whether a 64-bit multiply overflowed, the compiler needs those top 64 bits — but on a 16-bit CPU it cannot just widen to a 128-bit multiply: the multiply legalizer is deliberately clamped to 32 bits (the source comments warn that "lowering S128 to S64 would produce infinite regress"). So the 128-bit product is hand-composed from 32-bit __mulsi3 partial products plus __muldi3 glue. This demo is the first in the battery to make the compiler form an s64 multiply-high at all. |
| the coverage-check that found it | Round 6 stops hunting new corners and instead re-stresses bugs already fixed — auditing which code paths each fix actually exercises. An audit of the legalizer against the five shipped 64-bit demos showed s64 add/sub/mul/divide/shift/convert were all already covered — but nothing formed an s64 multiply-high or multiply-overflow. This demo closes that one gap. |
| distinct from #76 (s16/s32) | Demo #76 smulorbit used __builtin_mul_overflow on int16_t and int32_t (G_SMULO at 16/32 bits). At those widths the overflow check widens to a 32- or 64-bit multiply that the compiler already supports directly. At 64 bits there is no wider native multiply to widen into — that is what makes the s64 path a separate, previously-untested lowering. |
| distinct from #56 (s32 mul-high) and #22 (low 64) | Demo #56 rotozoom takes the high half of a 32-bit multiply (G_UMULH at s32). Demo #22 avalanche uses __muldi3 — but only the LOW 64 bits of the product, never the high half. This demo is the first to need the HIGH 64 bits of a 64-bit product. |
| result: correct across every mode | The 64-bit overflow flags are computed identically on the host (native 64-bit) and on the SNES (the composed multiply-high). The gate folds a rolling overflow count into a CRC; host == default == +mos-a16 == +mos-xy16 on both MAME and bsnes-jg, -verify-machineinstrs clean. The one untested s64 legalizer path is correct — a clean positive that now guards against any future regression. |
Written in C with the llvm-mos 65816 toolchain and verified against bsnes-jg and MAME.
Hit Verify fidelity to reproduce the build gate's WRAM assert
(gate CRC 0x3A69 — a fold of 121 unsigned + signed 64-bit overflow checks) live in this tab.
No far pointers — host == default == +mos-a16 == +mos-xy16,
-verify clean.