SWI 0x12 — LZ77UnCompVram¶
- Entry:
0x00001194(ARM) - Status: verified (hardware-checked 2026-07-08: cycle counts measured and output round-trip-verified on real GBA via the worker ROM)
Summary¶
Decompresses an LZ77 stream into a destination that only accepts 16-bit (or wider) writes, such as VRAM, OAM, or palette RAM. It reconstructs the output byte-by-byte logically but buffers two bytes and writes them as one halfword.
Parameters¶
| Reg | In | Meaning |
|---|---|---|
| r0 | ptr | Source address pointing at the 4-byte compression header. |
| r1 | ptr | Destination address (halfword-aligned, halfword-writable). |
Returns¶
No return value.
Clobbered registers¶
r0–r3, ip (r12), lr. r4–r10 are saved/restored.
Hardware audit (2026-07-08, canary r4–r12 + CPSR snapshot): caller-visible clobbers: r0 advanced past the source stream; r1 = dst+0x104 for a 0x100-byte output; r3 = 0. r2, r4–r12, r13, and CPSR (flags and mode) came back bit-identical on every tested path. r11/r12 are explained by the SWI dispatcher (it pushes {r11, r12, lr} — see 10_irq_boot_and_iwram.md) and CPSR by the SPSR restore on return; r2 and r4–r10 must be preserved or restored by the routine itself — where a static note above claims r2 is destroyed, the hardware disagrees at the caller level. Raw data: results/clobber_audit_pass2.csv.
Algorithm¶
Header and flags handling are identical to the Wram variant (SWI 0x11): the
same LZ77 token encoding — literal (flag 0) and back-reference
length=(byte0>>4)+3, distance=((byte0&0x0F)<<8|byte1)+1 — is decoded.
The difference is output. A 16-bit accumulator and a byte-slot toggle (low byte / high byte) are maintained: - Each produced byte is OR-ed into the accumulator at the current slot (bit 0–7 for the first byte of a pair, bit 8–15 for the second). - The slot toggles after every byte. When it returns to the low slot (i.e. after every second byte) the full 16-bit accumulator is stored to the destination and the accumulator is cleared.
Because already-written output is only readable as halfwords, back-references
read a halfword from memory and extract the wanted byte by computing the
halfword offset (distance converted to an even byte offset) and the byte-select
shift (which byte of that halfword), driven by the current output parity.
Edge cases & known bugs¶
- Decompressed size 0: shared header check returns "skip"; nothing written.
- Odd decompressed size: because a store happens only when the pair is complete, an odd final byte is left pending in the accumulator and is never written. VRAM output sizes are normally even, so this rarely bites, but a compressed blob declaring an odd size loses its last byte.
- Back-reference into the not-yet-flushed current halfword: the referenced
byte is fetched from memory, but the pending (unstored) byte of the current
halfword lives only in the internal accumulator. A back-reference whose target
falls inside the current, not-yet-stored halfword (e.g. distance 1 while the
high byte is being assembled) reads stale VRAM instead of the intended byte.
Correct encoders for the Vram variant must avoid such references. This is the
key behavioural difference from the Wram variant. Hardware-captured: a
disp=1, len=7 reference immediately after one literal (into pre-zeroed
destination memory) produced
10 00 00 00 00 00 00 00instead of eight0x10bytes — the literal survives via the accumulator flush, and every back-referenced byte reads the old memory contents (0x00), which then propagate. - Type nibble not enforced; source-region protection identical to SWI
0x11 (source in
0x00000000–0x01FFFFFFis refused). - Length overshoot on malformed input behaves as in the Wram variant.
- Odd-size drop hardware-confirmed (2026-07-08 anomaly tests,
results/cycles_phase3_anomaly.csv): declared size 255 wrote bytes 0–253 correctly and left byte 254 untouched (destination sentinel intact) — the pending odd byte is silently discarded, as predicted.
Cycle count¶
Hardware-measured net CPU cycles (worker-ROM harness, TM0/TM1 cascade at F/1,
13-cycle baseline subtracted; identical across 3 runs — see
02_hardware_verification_checklist.md § Measurement setup).
Same 256-byte test vectors as SWI 0x11, EWRAM src/dst: mixed stream 7263; VRAM-safe stream (disp ≥ 2, 220 bytes) 7271 — ≈1.3× the Wram variant, the halfword buffering overhead. Round trip verified with the disp ≥ 2 stream.
Open questions (need hardware verification)¶
- ~~Exact stale-read value of an in-pending-halfword back-reference~~ — captured: it reads whatever the destination memory held before (see the hardware-captured example in Edge cases); effectively "old destination contents", undefined from the decompressor's point of view.
- Whether real compressed assets ever declare odd sizes for this SWI.
GBATEK cross-reference¶
Matches GBATEK's LZ77 format. Adds precise description of the halfword-buffering scheme, the dropped-final-byte behaviour on odd sizes, and the pending-halfword back-reference hazard.