Brainfuck Threaded-Code VM

Games & Classics

A Brainfuck interpreter running live on a Super Nintendo, dispatching each of its eight opcodes with a computed gotogoto *handlers[op], the "threaded code" loop that lowers to a single indirect jump. Watch the program pointer scrub the source, the tape cells mutate, and the output marquee spell out HELLO WORLD! one byte at a time. Runs automatically; no joypad needed. Written in C and compiled with the llvm-mos-based 65816 toolchain (+mos-a16, 16-bit accumulator mode).

loading core…

Self-running demo — the VM interprets Hello World on a loop.

Click the screen to focus, then watch. Tab away and it pauses.

What it is

Brainfuck is a minimal language with just eight instructions — > < + - . , [ ] — operating on a byte tape and a data pointer. This demo compiles the canonical "Hello World!" program to an opcode stream and runs it through a threaded-code interpreter: instead of a switch, each opcode is an index into a table of label addresses, and dispatch is a direct computed jump:

static const void *handlers[] = { &&h_incp, &&h_inc, ... };
#define NEXT()  goto *handlers[prog[pc++]]   // ← the computed goto

h_incp: dp++;            NEXT();   // '>'
h_inc:  tape[dp]++;      NEXT();   // '+'
h_out:  emit(tape[dp]);  NEXT();   // '.'
h_jz:   if(!tape[dp]) pc = match+1;  NEXT();   // '['

On screen you see the compiled program with the current instruction (the "tape head") highlighted in cyan, the 64-cell tape as an ASCII heat grid with the data pointer marked, the output marquee filling in, and live step / pointer counters. Every operator except input runs, driving thousands of computed-goto dispatches per pass.

Compiler stress-test

ItemWhat it exercises
goto *handlers[op]GNU C labels-as-values dispatch — the opcode indexes a table of &&label addresses and control jumps straight there. This is the "threaded code" interpreter, the fastest dispatch shape.
indirect jmpOn the 65816 the computed goto lowers to a real indexed-indirect jump — jmp ($addr,x) (opcode $7C) — not a switch/branch chain. That indirect jump is the whole point of the demo.
vs. a switch VMA dense switch (like the Turtle VM #29a) lowers to a contiguous jump table; a computed goto threads per-op through an address table. Different codegen path, different bugs hide there.
rep/sepMode-switch brackets mark 16-bit accumulator sections under +mos-a16 (the pc/dp pointer arithmetic). Pure control flow otherwise — no 32-bit multiply or divide anywhere.

This is the corner a switch-based VM does not open. The whole reason the demo exists is to exercise the backend's lowering of LLVM indirectbr / blockaddress on the 65816 — and it does so correctly: the computed goto compiles to a genuine indexed-indirect jump across the default 8-bit, +mos-a16 and +mos-xy16 backends, all -verify-machineinstrs clean.

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 CRC 0x9954 — a rotate-XOR fold of the program's output + final tape) live in this tab. No far pointers — the same source passes every way: host == default == +mos-a16 == +mos-xy16 == bsnes-jg.