Borrow-Ladder Odometer
Big NumbersA 128-bit number counting down — subtracted limb by limb, the borrow rippling upward like an odometer rolling past zero. This is compiler stress-test #110: every subtract chain begins with a set carry bit the compiler must materialize just so, and a real llvm-mos fix (patch 0012) is what lowers that one-bit “no borrow yet” correctly. Eight limbs, exercised on every tick.
Self-running — a 128-bit binary counter ticks down, borrows rippling upward.
Click the screen, then play. (Tab away and it pauses.)
What it is
Numbers wider than the machine word are done in pieces, and subtraction threads a borrow from the bottom piece to the top. The awkward part for a compiler is the very start of the chain — setting the carry to mean “no borrow yet” — and that is exactly what this demo hammers.
// subtract two 128-bit numbers, limb by limb, borrow rippling upward
uint16_t borrow = 0;
for (int i = 0; i < 8; i++) {
int32_t t = a[i] - b[i] - borrow; // 16-bit subtract with a borrow-in
a[i] = (uint16_t)t;
borrow = (t < 0) ? 1 : 0; // borrow-out feeds the next limb
}
// the very first subtract needs its carry SET (no borrow yet) —
// a single-bit "carry = 1" the compiler must materialize just so. Each frame subtracts a large decrement, so borrows fire constantly and cascade up through all eight limbs. The gate folds the value into a CRC. The picture is the proof: a wrong borrow would break the countdown and change the CRC.
Compiler stress-test #110 — Round 6: hardening the fixes
| Item | What it exercises |
|---|---|
| subtracting big numbers | A 128-bit number is stored as eight 16-bit pieces. To subtract one from another you work piece by piece from the bottom, and whenever a piece would go negative you take a borrow from the next one up. That borrow ripples upward — the same way a car odometer rolls 30000 back to 29999. |
| the set carry bit | On the 65816, subtract uses the carry flag as an inverted borrow: before the first subtract you must SET the carry to say “no borrow yet”. That single set bit is a tiny value the compiler has to produce exactly right — a “carry = 1” materialized just before the subtract chain. |
| the bug this guards (patch 0012) | Producing that set carry bit — lowering a one-bit “true” into the actual set-carry instruction — is a narrow, easy-to-get-wrong step. Patch 0012 lowers it correctly. This demo builds a long borrow chain across eight limbs, so the set carry and the borrow ripple are exercised on every tick, and confirms the fix holds. |
| exact by construction | Multi-precision subtraction is exact integer arithmetic, identical on the host and on the SNES. A single dropped or duplicated borrow would send the countdown off the rails. The gate folds the running 128-bit value into a CRC and checks it matches — host == default == +mos-a16 == +mos-xy16, -verify-machineinstrs clean. |
| default and 16-bit modes | The fix lives in the 16-bit-accumulator modes, so those columns carry the weight, but the demo is checked all five ways on two emulators and every column stays green. |
| the visual | The 128 bits are shown as a grid — bright for 1, dark for 0. Each frame the odometer subtracts a big decrement and ticks down, so the low bits churn constantly while borrows ripple up into the higher bits, exactly the cascade the borrow chain produces. |
Compiler bug this demo guards against
Patch 0012 — a pre-existing gap in the machine-code lowering for carry-flag immediates:
the lowering only handled the encodings for "clear" (0) and "set" (-1), but a set carry-in
can also arrive as the literal value 1 (e.g. the carry-in for a 16-bit subtract) — hitting an
unreachable assertion on debug builds, and undefined behaviour (that happened to
still produce the right instruction) on release builds. This was surfaced only once a separate fix
(patch 0011) let compilation reach this stage at all.
Fix: Lower any nonzero carry-flag immediate as "set carry", not just -1 — a differential-neutral fix.
This demo's 128-bit descending odometer, built from eight chained 16-bit subtract-with-borrow limbs, exercises exactly the carry-in pattern that triggered the bug, as a standing regression guard.
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 0x1BE3 — a fold of 160 countdown steps) live in this tab.
No far pointers — host == default == +mos-a16 == +mos-xy16,
-verify clean.