Guest GPRs were reloaded from PpcContext memory on every use and stored back after every write, forcing memory traffic that held the JIT bodies at mere interpreter parity. Now each GPR is loaded ONCE per block into a Cranelift SSA value (kept in a host register by the register allocator); writes update an in-EmitCtx cache (RegCache: [Option<Value>;32] + dirty bits) without storing; dirty GPRs are flushed to memory only at the block boundary (cache_flush at the end of emit_node_body). Sound because nothing a block calls back into touches guest GPRs: the memory trampolines take addr/val as args (I pass the cached SSA values), and the FP-punt runs interpreter FP ops that touch fpr/fpscr/cr only. So the cache never goes stale mid-block; flushing at the boundary makes gpr[] interpreter-identical for the runner and the next block. FPR/CR still go through memory (the FP-punt writes them, which would need flush-around-punt — deferred). emit_op / ea_d / ea_x / gpr32 / store_gpr32z rewired to cache_read_gpr / cache_write_gpr; cache_read_gpr copies the Option out before re-borrowing (a RefCell double-borrow would panic). Validated: 7/7 jit unit tests; diff MISMATCHES=0 over 47.9M blocks (foreground). Perf (foreground, RUST_LOG=warn, -n 3e9 to amortize compile cost — -n<=1.5e9 is compile-dominated and misleads): interp 95.8 MIPS -> jit-no-chain 100.2 MIPS (bodies now BEAT interp, were parity) -> chain 100.4 MIPS / 57.95s = -15.6% wall vs interp (was ~-6% at B2), dispatch 18.9%, 110M calls (1.95x merge). Same-batch -n 2e9: interp 44.8s -> chain 41.0s = -8.6%. Register caching is what makes the bodies fast enough that chaining's dispatch cut converts to real wall time (B2's bodies had slowed to ~82 MIPS and cancelled the win). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>