Master the fundamental concepts of emulation through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksDynamic recompilation (dynarec) translates guest machine code to host machine code at runtime, caching translated blocks. It powers high-performance console emulators when interpretive CPUs are too slow.
Workflow:
Challenges beyond template JIT:
For example, translating a MIPS block from a PS1 emulator into x86-64 once, then reusing it for every loop iteration, removes interpret overhead entirely.
Profile hot PCs first; cold code can stay interpreted.
Invalidate cached blocks when guest code writes to memory that might contain instructions. Self-modifying code and JIT caches interact badly if you assume ROM is always read-only.
This exercise asks you to document or prototype a dynarec block cache for a simple guest ISA. You will explain block translation, caching by program counter, and invalidation when guest code changes.
Model the heart of a dynamic recompiler (QEMU's TCG, Dolphin's JIT) for a tiny guest ISA:
Since we can't emit host code here, "executing a translation" just runs the block's instructions. Everything else follows a real dynarec's bookkeeping, and the statistics show why chaining matters.
cLoading…
TARGET is a label or an instruction index. # starts a comment. Arithmetic wraps at 32 bits, and blt is signed.
beq/bne/blt/jmp/halt, 16 instructions, or the end of the code, whichever comes first. It takes the lowest free slot in a 64-entry cache table. Print translate @PC: N instruction(s) [START-END]. Blocks may overlap: a jump into the middle of a block translates a new block there.jmp) and a fall exit (the next instruction). After the block, if that exit is linked, go straight to the linked block (a chained jump: no lookup). Otherwise, dispatch. Then, with chaining on, link the exit to the found block and print chain @FROM.jump|fall -> @TO.poke T = V. Every live block covering T is invalidated, in slot order, printing invalidate @START (write to T), and every link into it is cleared. If the running block itself died, it stops right after the poke and dispatches the next instruction, without linking.halt ends the run. After 100000 guest instructions, the run stops.cLoading…
stopped at the 100000-instruction limit instead of halted when the limit ends the run. The translation and invalidation counts use the singular for 1.instruction N: cannot parse "TEXT" (the instruction still takes its index)instruction N: unknown target Xcode has errors (for run)no codeunknown command XInput:
cLoading…
Output:
cLoading…
Hidden tests cover self-modifying code that rewrites a loop bound in another block, a block that invalidates itself, re-running with already-patched code, chaining turned off, the step limit, and program errors.