In-Place Block Rotate

Algorithms & Data

A 384-entry uint16_t buffer rotated in place by the classic three-reversal trick — rev[0,k) · rev[k,n) · rev[0,n) — so the reversal’s buf[lo] ↔ buf[hi] swaps sweep 16-bit indices back and forth across the register-width boundary. This is compiler stress-test #94: it re-runs the scenario a real llvm-mos bug once corrupted — a live 16-bit index under +mos-xy16 — from a different angle than the memmove demo, with no library call in sight, to confirm the fix holds.

loading core…

Self-running — the barber-pole marquee rotates in place, one step per frame.

Click the screen, then play. (Tab away and it pauses.)

What it is

Rotating an array is often written with a scratch copy. It doesn’t need one: reverse the first k elements, reverse the remaining n−k, then reverse the whole thing, and the array comes out rotated left by k — entirely in place. Each reversal swaps pairs working inward from both ends, which is exactly where the index arithmetic gets exercised.

// rotate a 384-entry uint16_t buffer LEFT by k, all in place —
// via the three-reversal identity  rev[0,k) . rev[k,n) . rev[0,n)
void reverse(uint16_t *buf, uint16_t lo, uint16_t hi) {
    while (lo + 1 < hi) {              // lo, hi are 16-bit indices (0..383)
        hi--;
        uint16_t t = buf[lo];          // 16-bit indexed load
        buf[lo] = buf[hi];
        buf[hi] = t;                   // ...and store, past the 256 boundary
        lo++;
    }
}
void rotate_left(uint16_t *buf, uint16_t n, uint16_t k) {
    reverse(buf, 0, k);  reverse(buf, k, n);  reverse(buf, 0, n);
}

The buffer holds 384 sixteen-bit values, so every buf[lo] and buf[hi] is a 16-bit-indexed access, past the 256-element boundary. Each step rotates by a small runtime k and folds all 384 entries into a rolling CRC. The picture is the proof: any corruption of a 16-bit index would swap the wrong element, tear the barber-pole, and change the CRC.

Compiler stress-test #94 — Round 6: hardening the fixes

ItemWhat it exercises
rotate by three reversalsRotating an array left by k is three reversals: reverse the first k, reverse the rest, then reverse the whole thing. It needs no scratch buffer — the whole rotation happens in place, swapping pairs from the outside in. Classic, O(1) extra space, and a tidy stress of index arithmetic.
384 entries, on purpose (the 256 boundary)The 65816 addresses memory with 8-bit or 16-bit index registers; a table with more than 256 entries forces the 16-bit-index path. This buffer holds 384 uint16_t values precisely so every buf[lo]/buf[hi] access in the reversal is a 16-bit-indexed read and write — the exact register-width regime a real compiler bug once mishandled.
the bug this guards (#23, patch 0002)When the index registers run 16-bit (the +mos-xy16 mode), an earlier version of the compiler could place a "switch index back to 8-bit" instruction (sep #$10) in the middle of a live 16-bit index — zeroing its high byte. The fix (MOSInsertREPSEP::placeIntraBlock) reloads the index after each width switch. Demo #93 re-ran that via the C memmove library call; this one re-runs it a different way — a hand-written swap loop whose 16-bit buffer accesses are bracketed by the same rep/sep width transitions the fix schedules — so both angles onto the same fix are guarded.
a measured aside — the modulo vanishesThe rotate normalises k with k %= n. Because the demo always feeds a small k (never more than 34) and n is 384, the compiler proves k % 384 == k and deletes the divide entirely — no __umodhi call is emitted. That is a correct optimization, not a bug; the demo’s real target is the width-flag handling around the 16-bit buffer access, not the modulo.
result: bit-exact in every modeA rotate is a pure permutation — it only moves values, so the host and the SNES must agree exactly. The gate folds all 384 entries each step (position-sensitive, so the moved data matters) into a rolling CRC: host == default == +mos-a16 == +mos-xy16 on both MAME and bsnes-jg, -verify-machineinstrs clean. A dropped index high byte would swap the wrong element, tear the barber-pole, and change the CRC. It does not — the fix holds, now permanently regression-guarded.
the visualThe 384 entries map to a 16×24 grid; each cell’s colour is a diagonal stripe baked into the top bits of its value. Rotating the 1-D buffer marches those stripes across the grid — a barber-pole marquee that shears smoothly, one small rotation per frame, without tearing.

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 0xB93A — a fold of 12 steps × 384 entries) live in this tab. No far pointers — host == default == +mos-a16 == +mos-xy16, -verify clean.