Gap-Buffer Rope Editor
Algorithms & Data
A text gap buffer — the data structure inside many real editors — whose cursor moves
memmove the text across the gap, both directions and more than 256 bytes
at a time. This is compiler stress-test #96, the final Cluster-A demo: it runs the exact
scenario a real llvm-mos bug once corrupted
— a 16-bit-indexed copy under +mos-xy16 — at scale, as a working editor primitive
rather than a synthetic loop.
Self-running — text blocks slide across the gap as the cursor jumps and edits.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
In a gap buffer the characters live on either side of a movable empty region at the cursor.
Typing and deleting only touch the gap’s edge and are cheap. The one costly operation is a
long cursor jump: all the text between the old and new position must be copied across the gap
with memmove, which handles the overlapping ranges safely.
// a gap buffer: text lives on either side of a movable "gap"
// text = buf[0, gap_start) ++ buf[gap_end, N)
uint8_t buf[576]; uint16_t gap_start, gap_end;
insert(ch): buf[gap_start++] = ch; // typing fills the gap
delete(): gap_start--; // backspace grows the gap
move(pos): // move the cursor — memmove text across the gap, EITHER way
if (pos < gap_start) { // cursor left -> shift text right
n = gap_start - pos;
memmove(&buf[gap_end - n], &buf[pos], n); // n can exceed 256 bytes
} else { // cursor right -> shift text left
n = pos - gap_start;
memmove(&buf[gap_start], &buf[gap_end], n);
} The buffer holds 576 bytes with ~480 characters typed in, so a cursor jump copies more than 256 bytes — a 16-bit-indexed move. A scripted edit stream drives jumps in both directions and folds all 576 bytes into a rolling CRC each step. The picture is the proof: a wrong index would smear the text and change the CRC.
Compiler stress-test #96 — Round 6: hardening the fixes
| Item | What it exercises |
|---|---|
| the gap buffer | A gap buffer is how many real text editors store a line: the characters sit on either side of an empty “gap” at the cursor. Typing writes into the gap; backspace enlarges it. The only expensive operation is moving the cursor a long way — that memmoves all the text between the old and new cursor across the gap. |
| memmove, both directions, at scale | Moving the cursor left shifts text toward the high end; moving it right shifts text toward the low end — so both memmove copy directions fire. With a 576-byte buffer holding ~480 characters, a cursor jump moves more than 256 bytes, which forces the copy onto the 65816’s 16-bit-index path. |
| the 256-byte boundary | The 65816 addresses memory with 8-bit or 16-bit index registers. A copy of ≤256 bytes fits an 8-bit index; more than that needs a 16-bit index. The buffer is sized past 256 so every cursor-move memmove is driven by a 16-bit index — the exact path a real compiler bug once corrupted. |
| the bug this guards (#23, patch 0002) | Under 16-bit-index mode (+mos-xy16), an earlier compiler could slip a “switch index to 8-bit” instruction (sep #$10) between writing a 16-bit index and reading it, zeroing the high byte and mis-addressing the copy. The fix reloads the index after each width switch. This is the fourth and final Cluster-A demo guarding it — after a synthetic memmove, an indirect-pointer reversal, and an indexed store, this one exercises memmove as a real editor primitive, at scale, in both directions. |
| result: bit-exact in every mode | A gap buffer is a pure byte shuffle — identical on the host and on the SNES. A scripted edit stream (type / delete / jump) runs, and the gate folds all 576 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 smear the text and change the CRC. It does not — the fix holds, now permanently regression-guarded. |
| the visual | The 576-byte buffer is drawn as a 16×16 window of colour cells (colour by byte class). As the cursor jumps around and text is typed and deleted, the coloured text blocks slide across the gap — the memmoves made visible. |
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 0x2361 — a fold of 20 edit steps × 576 bytes) live in this tab.
No far pointers — host == default == +mos-a16 == +mos-xy16,
-verify clean.