Duff Dissolve
Algorithms & Data
One image dissolving into the next on a Super Nintendo, each tile copied by the notorious
Duff's device — a switch
whose case labels land in the middle of a loop body. The switch and the
loop back-edge interlace into irreducible control flow — a branch tangle a structurizer
can't untangle. Runs automatically; no joypad needed. Written in C and compiled with the
llvm-mos-based 65816 toolchain
(+mos-a16, 16-bit accumulator mode).
Self-running demo — one image dissolves into the next in scattered bursts.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
Duff's device is a famous trick for unrolling a copy loop: you handle the remainder by
jumping into the middle of the unrolled body. The switch picks the entry
point and the loop carries on from there — the case labels sit inside the loop:
switch (count % 8) {
case 0: do { *to++ = *from++; // ← the loop body...
case 7: *to++ = *from++;
case 6: *to++ = *from++;
... // ...and the switch's
case 1: *to++ = *from++; // cases land INSIDE it
} while (--n > 0);
} Because the switch jumps into the loop, the two overlap in a way that can't be rewritten as clean nested blocks — irreducible control flow. Here it copies image tiles in a scattered order, dissolving one picture into the next. The whole tangle stays bit-exact across every codegen mode.
Compiler stress-test
| Item | What it exercises |
|---|---|
| switch into a loop | The case labels of the switch fall in the middle of a do/while loop body. On entry the switch jumps straight to the right case, then the loop back-edge carries execution around — the two interlace. |
| irreducible CFG | That interlacing is control flow that cannot be rewritten as clean nested if/while blocks — an irreducible graph. Compilers that rely on a structurizer choke on it; a direct branch lowering must handle the tangle. No other demo in the battery exercises it. |
| branch mesh | On the 65816 the tangle lowers to a dense mesh of branches — 19 jmp targets and 19 bne back-edges in the corpus slice — with zero arithmetic libcalls (the /8 and %8 fold to shift and mask). The differential proves every branch resolves identically across default-8-bit, +mos-a16 and +mos-xy16. |
| dissolve | The device drives the visuals: each frame reveals three more tiles in a scattered (bit-reversed) order, each tile's bytes copied by the unrolled loop — one image dissolving into the next, then the pattern flips (diagonal bands ⇄ concentric rings). |
Written in C with the llvm-mos 65816 toolchain and verified against bsnes-jg
(and MAME where the SPC700 IPL is present). Hit Verify fidelity to reproduce the build
gate's WRAM assert (gate hash 0x5531 — a fold of Duff-copied buffers of every length
1–40) live in this tab. No far pointers — the same source passes every way: host == default ==
+mos-a16 == +mos-xy16 == bsnes-jg, -verify clean.