Overlap-Move Mosaic
Algorithms & Data
A 16×24 = 384-byte mosaic scrolled in place by four overlapping
memmove calls every step — two descending (dst>src), two ascending
(dst<src), each over more than 256 bytes so the copy must use a 16-bit index register.
This is compiler stress-test #93: it re-runs the exact scenario a real
llvm-mos bug once corrupted — an
in-place memmove under 16-bit-index mode — larger and in both directions, to
confirm the fix holds.
Self-running — the mosaic shears down-right, then up-left, as the memmoves alternate.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
memmove copies bytes safely even when the source and destination ranges
overlap — the case plain memcpy is not allowed to handle. The compiler proves
the direction: if the destination is above the source it copies high-to-low, otherwise
low-to-high. This demo exercises both directions, on a buffer deliberately sized past 256
bytes so each copy is driven by a 16-bit index register.
// a 16×24 = 384-byte mosaic, scrolled IN PLACE (dst and src overlap) uint8_t *flat = &cell[0][0]; // descending overlap (dst > src): scroll down / right memmove(&cell[1][0], &cell[0][0], 23*16); // 368 bytes memmove(flat + 1, flat, 384 - 1); // 383 bytes // ascending overlap (dst < src): scroll up / left memmove(&cell[0][0], &cell[1][0], 23*16); // 368 bytes memmove(flat, flat + 2, 384 - 2); // 382 bytes // every count > 256, so the copy must index with a 16-bit register.
Each step alternates a descending pair (scroll down + right) and an ascending pair (scroll up + left), then folds all 384 bytes into a rolling CRC. The picture is the proof: any corruption of the 16-bit index would both streak the mosaic and change the CRC.
Compiler stress-test #93 — Round 6: hardening the fixes
| Item | What it exercises |
|---|---|
| in-place overlapping memmove | memmove (unlike memcpy) is defined even when source and destination overlap — it must pick a copy direction so it never clobbers bytes it has not read yet. dst>src copies high-to-low (descending); dst<src copies low-to-high (ascending). This demo drives both directions, four memmoves per step, over the same 384-byte buffer. |
| the 256-byte boundary (why >256 matters) | The 65816 addresses memory with 8-bit or 16-bit index registers. A copy of ≤256 bytes fits an 8-bit index; a copy of more than 256 bytes needs a 16-bit index. This buffer is 384 bytes precisely so every memmove is forced onto the 16-bit-index path — the exact path a real compiler bug once corrupted. |
| the bug this guards (#23, patch 0002) | When the accumulator/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) between writing a 16-bit index and reading it — zeroing the index’s high byte and corrupting an in-place memmove. The fix reloads the index after switching width. This demo re-runs that exact scenario, larger and in both directions, and confirms the fix holds. |
| result: bit-exact in every mode | A memmove of a byte array is a pure data shuffle — identical on the host and on the SNES. The gate folds all 384 bytes each step into a CRC; host == default == +mos-a16 == +mos-xy16 on both MAME and bsnes-jg, -verify-machineinstrs clean. A dropped index high byte would streak the mosaic and change the CRC. It does not — the fix is correct and now permanently regression-guarded. |
| the visual | A 16×16 window of the mosaic is drawn as solid colour tiles. On descending steps the pattern shears down and right; on ascending steps it shears up and left. Fresh coloured rows/bytes are injected at the exposed edges, so the diagonal bands keep flowing. |
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 0xA990 — a fold of 16 steps × 384 bytes) live in this tab.
No far pointers — host == default == +mos-a16 == +mos-xy16,
-verify clean.