From bb2ee2c0ea9cfc221565f50de32779ceb45d8d7a Mon Sep 17 00:00:00 2001 From: Joe Mattiello Date: Sun, 1 Nov 2026 16:21:07 -0500 Subject: [PATCH] perf(gpu): direct-threaded dispatch inside GPUExec (#569) (#1140) GPUExec dispatched every emulated instruction through executeOpcode(): a call, a return and the register spills either side of them, per op. The function could never be inlined into the loop: it holds label addresses (computed goto), and it is recursive -- jump/jr run their delay slot through it. GPUExec now dispatches through its own computed-goto table to labels inside GPUExec, where the (GPU_OP_HOT) handlers are inlined; the loop's post-op bookkeeping follows unchanged. executeOpcode() stays exactly as it was for the delay slot. One X-macro, GPU_OPCODE_LIST, generates both tables and both label blocks, so the two copies cannot drift (it also replaces executeOpcode's hand-written 64-entry table). Non-GNU compilers keep the executeOpcode() call. Emulated output unchanged: frame_hash_ab CSVs byte-identical to develop over 300 Doom gameplay frames and 400 yarc frames; gpu_idle_probe_falsify passes (the idle probe keys on index 53 after dispatch, as before). Measured on top of #1137, Doom gameplay from one savestate: - cachegrind (ir_ab.sh --ref, 300 frames): Ir -4.07%, D1 miss -4.13%, I1 miss +2.80% (GPUExec is larger), LL +0.16%. - Mac Studio M4 Max wall, test_benchmark 900 frames, A/B/B/A x3: avg 3.60 -> 3.38 ms (-6.1%), p99 4.3 -> 4.0 ms, every B run faster than every A run; yarc 3.39 -> 3.34 ms (-1.6%). Co-authored-by: Claude Fable 5.1 --- src/tom/gpu.c | 193 +++++++++++++++++++++++++------------------------- 1 file changed, 97 insertions(+), 96 deletions(-) diff --git a/src/tom/gpu.c b/src/tom/gpu.c index c3485cbc..d8d237cc 100644 --- a/src/tom/gpu.c +++ b/src/tom/gpu.c @@ -507,6 +507,74 @@ void GPUUpdateRegisterBanks(void); #define GPU_OP_HOT INLINE static #endif +/* The 64 RISC opcodes in encoding order (index = opcode >> 10): one list, used + * for both dispatch tables below so they cannot drift apart. */ +#define GPU_OPCODE_LIST(X) \ + X(add) \ + X(addc) \ + X(addq) \ + X(addqt) \ + X(sub) \ + X(subc) \ + X(subq) \ + X(subqt) \ + X(neg) \ + X(and) \ + X(or) \ + X(xor) \ + X(not) \ + X(btst) \ + X(bset) \ + X(bclr) \ + X(mult) \ + X(imult) \ + X(imultn) \ + X(resmac) \ + X(imacn) \ + X(div) \ + X(abs) \ + X(sh) \ + X(shlq) \ + X(shrq) \ + X(sha) \ + X(sharq) \ + X(ror) \ + X(rorq) \ + X(cmp) \ + X(cmpq) \ + X(sat8) \ + X(sat16) \ + X(move) \ + X(moveq) \ + X(moveta) \ + X(movefa) \ + X(movei) \ + X(loadb) \ + X(loadw) \ + X(load) \ + X(loadp) \ + X(load_r14_indexed) \ + X(load_r15_indexed) \ + X(storeb) \ + X(storew) \ + X(store) \ + X(storep) \ + X(store_r14_indexed) \ + X(store_r15_indexed) \ + X(move_pc) \ + X(jump) \ + X(jr) \ + X(mmult) \ + X(mtoi) \ + X(normi) \ + X(nop) \ + X(load_r14_ri) \ + X(load_r15_ri) \ + X(store_r14_ri) \ + X(store_r15_ri) \ + X(sat24) \ + X(pack) + GPU_OP_HOT void gpu_opcode_add(void); GPU_OP_HOT void gpu_opcode_addc(void); GPU_OP_HOT void gpu_opcode_addq(void); @@ -2774,7 +2842,30 @@ void GPUExec(int32_t cycles) * charged, via the same gpu_bus_stall channel as DRAM costs). */ if (pipeTiming) GPUPipeCheckUse(index); +#ifdef __GNUC__ + /* Direct-threaded dispatch (perf pass 2026-11): the handler runs as a + * label inside this function, not through executeOpcode(). That call + * was a call and a return per emulated instruction plus the spills + * either side of it, and executeOpcode() could never be inlined here + * because it holds label addresses and is recursive (jump/jr run their + * delay slot through it -- they still do, see gpu_opcode_jump). The + * same GPU_OPCODE_LIST builds both tables, so the two copies of the + * handlers always agree. */ + { + static const void *const gpu_exec_dispatch[64] = { +#define GPU_X(name) &&gpu_exec_op_##name, + GPU_OPCODE_LIST(GPU_X) +#undef GPU_X + }; + goto *gpu_exec_dispatch[index]; +#define GPU_X(name) gpu_exec_op_##name: gpu_opcode_##name(); goto gpu_exec_dispatched; + GPU_OPCODE_LIST(GPU_X) +#undef GPU_X + } + gpu_exec_dispatched: ; +#else executeOpcode(index); +#endif /* Issue #811: the previous instruction was an external load and * this one overwrote its target -- the load data lands last. */ if (ldxArmed) @@ -2889,106 +2980,16 @@ INLINE static void executeOpcode(uint32_t index) { * dispatch and lets the branch predictor track each opcode * independently. */ static const void *gpu_dispatch[64] = { - &&gpu_op_add, &&gpu_op_addc, - &&gpu_op_addq, &&gpu_op_addqt, - &&gpu_op_sub, &&gpu_op_subc, - &&gpu_op_subq, &&gpu_op_subqt, - &&gpu_op_neg, &&gpu_op_and, - &&gpu_op_or, &&gpu_op_xor, - &&gpu_op_not, &&gpu_op_btst, - &&gpu_op_bset, &&gpu_op_bclr, - &&gpu_op_mult, &&gpu_op_imult, - &&gpu_op_imultn, &&gpu_op_resmac, - &&gpu_op_imacn, &&gpu_op_div, - &&gpu_op_abs, &&gpu_op_sh, - &&gpu_op_shlq, &&gpu_op_shrq, - &&gpu_op_sha, &&gpu_op_sharq, - &&gpu_op_ror, &&gpu_op_rorq, - &&gpu_op_cmp, &&gpu_op_cmpq, - &&gpu_op_sat8, &&gpu_op_sat16, - &&gpu_op_move, &&gpu_op_moveq, - &&gpu_op_moveta, &&gpu_op_movefa, - &&gpu_op_movei, &&gpu_op_loadb, - &&gpu_op_loadw, &&gpu_op_load, - &&gpu_op_loadp, &&gpu_op_load_r14_indexed, - &&gpu_op_load_r15_indexed,&&gpu_op_storeb, - &&gpu_op_storew, &&gpu_op_store, - &&gpu_op_storep, &&gpu_op_store_r14_indexed, - &&gpu_op_store_r15_indexed,&&gpu_op_move_pc, - &&gpu_op_jump, &&gpu_op_jr, - &&gpu_op_mmult, &&gpu_op_mtoi, - &&gpu_op_normi, &&gpu_op_nop, - &&gpu_op_load_r14_ri, &&gpu_op_load_r15_ri, - &&gpu_op_store_r14_ri, &&gpu_op_store_r15_ri, - &&gpu_op_sat24, &&gpu_op_pack +#define GPU_X(name) &&gpu_op_##name, + GPU_OPCODE_LIST(GPU_X) +#undef GPU_X }; goto *gpu_dispatch[index]; - gpu_op_add: gpu_opcode_add(); return; - gpu_op_addc: gpu_opcode_addc(); return; - gpu_op_addq: gpu_opcode_addq(); return; - gpu_op_addqt: gpu_opcode_addqt(); return; - gpu_op_sub: gpu_opcode_sub(); return; - gpu_op_subc: gpu_opcode_subc(); return; - gpu_op_subq: gpu_opcode_subq(); return; - gpu_op_subqt: gpu_opcode_subqt(); return; - gpu_op_neg: gpu_opcode_neg(); return; - gpu_op_and: gpu_opcode_and(); return; - gpu_op_or: gpu_opcode_or(); return; - gpu_op_xor: gpu_opcode_xor(); return; - gpu_op_not: gpu_opcode_not(); return; - gpu_op_btst: gpu_opcode_btst(); return; - gpu_op_bset: gpu_opcode_bset(); return; - gpu_op_bclr: gpu_opcode_bclr(); return; - gpu_op_mult: gpu_opcode_mult(); return; - gpu_op_imult: gpu_opcode_imult(); return; - gpu_op_imultn: gpu_opcode_imultn(); return; - gpu_op_resmac: gpu_opcode_resmac(); return; - gpu_op_imacn: gpu_opcode_imacn(); return; - gpu_op_div: gpu_opcode_div(); return; - gpu_op_abs: gpu_opcode_abs(); return; - gpu_op_sh: gpu_opcode_sh(); return; - gpu_op_shlq: gpu_opcode_shlq(); return; - gpu_op_shrq: gpu_opcode_shrq(); return; - gpu_op_sha: gpu_opcode_sha(); return; - gpu_op_sharq: gpu_opcode_sharq(); return; - gpu_op_ror: gpu_opcode_ror(); return; - gpu_op_rorq: gpu_opcode_rorq(); return; - gpu_op_cmp: gpu_opcode_cmp(); return; - gpu_op_cmpq: gpu_opcode_cmpq(); return; - gpu_op_sat8: gpu_opcode_sat8(); return; - gpu_op_sat16: gpu_opcode_sat16(); return; - gpu_op_move: gpu_opcode_move(); return; - gpu_op_moveq: gpu_opcode_moveq(); return; - gpu_op_moveta: gpu_opcode_moveta(); return; - gpu_op_movefa: gpu_opcode_movefa(); return; - gpu_op_movei: gpu_opcode_movei(); return; - gpu_op_loadb: gpu_opcode_loadb(); return; - gpu_op_loadw: gpu_opcode_loadw(); return; - gpu_op_load: gpu_opcode_load(); return; - gpu_op_loadp: gpu_opcode_loadp(); return; - gpu_op_load_r14_indexed:gpu_opcode_load_r14_indexed();return; - gpu_op_load_r15_indexed:gpu_opcode_load_r15_indexed();return; - gpu_op_storeb: gpu_opcode_storeb(); return; - gpu_op_storew: gpu_opcode_storew(); return; - gpu_op_store: gpu_opcode_store(); return; - gpu_op_storep: gpu_opcode_storep(); return; - gpu_op_store_r14_indexed:gpu_opcode_store_r14_indexed();return; - gpu_op_store_r15_indexed:gpu_opcode_store_r15_indexed();return; - gpu_op_move_pc: gpu_opcode_move_pc(); return; - gpu_op_jump: gpu_opcode_jump(); return; - gpu_op_jr: gpu_opcode_jr(); return; - gpu_op_mmult: gpu_opcode_mmult(); return; - gpu_op_mtoi: gpu_opcode_mtoi(); return; - gpu_op_normi: gpu_opcode_normi(); return; - gpu_op_nop: gpu_opcode_nop(); return; - gpu_op_load_r14_ri: gpu_opcode_load_r14_ri(); return; - gpu_op_load_r15_ri: gpu_opcode_load_r15_ri(); return; - gpu_op_store_r14_ri: gpu_opcode_store_r14_ri(); return; - gpu_op_store_r15_ri: gpu_opcode_store_r15_ri(); return; - gpu_op_sat24: gpu_opcode_sat24(); return; - gpu_op_pack: gpu_opcode_pack(); return; /* NOLINT(readability-redundant-control-flow) -- goto target */ +#define GPU_X(name) gpu_op_##name: gpu_opcode_##name(); return; + GPU_OPCODE_LIST(GPU_X) +#undef GPU_X #else /* Switch fallback for MSVC and other non-GNU compilers */ switch (index) {