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 <[email protected]>
This commit is contained in:
Joe MattielloandClaude Fable 5.1 authored and GitHub committed 2026-11-01 16:21:07 -05:00
1 parent b242c67911
commit bb2ee2c0ea
1 file changed
+97 -96
+97 -96
View File
@@ -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) {