[iterate-4C] JIT: gated fallback-opcode histogram (XENIA_JIT_STATS)

jit_interpret_one tallies fallback executions per opcode (behind
XENIA_JIT_STATS, off by default); dump_fallback_stats() prints the top 30
after a check run. Measured on Sylpheed n100M: the fallback tax is FP-
dominated — lfs/stfs/lfsx/stfsx ~50%, fmadds/fmuls/fadds/fmr ~21%,
ld 4.5%, shifts ~4%, mtspr/mfspr only ~3%. Refutes the earlier
"mflr/mtlr dominates" guess; FP is the crossover lever.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-04 20:14:46 +02:00
parent e8d0dc4a2d
commit afc3692223
2 changed files with 41 additions and 0 deletions

View File

@@ -1998,6 +1998,10 @@ fn cmd_exec_inner(
info!("run digest matches golden");
}
}
// Diagnostic (XENIA_JIT_STATS): dump the JIT fallback-opcode
// histogram so we can see which un-ported opcodes dominate. No-op
// unless the env var is set.
xenia_jit::dump_fallback_stats();
Ok(())
})()
};

View File

@@ -91,11 +91,48 @@ unsafe extern "C" fn jit_interpret_one(env: *mut JitEnv, instr: *const DecodedIn
let ctx = unsafe { &mut *env.ctx };
let mem: &dyn MemoryAccess = unsafe { &*env.mem };
let instr = unsafe { &*instr };
if stats_enabled() {
// Diagnostic (XENIA_JIT_STATS): tally which opcodes fall back, to pick
// the next ports. Off by default (a global lock per fallback is slow).
*fallback_hist().lock().unwrap().entry(instr.opcode).or_insert(0) += 1;
}
let r = interpret_one(ctx, mem, instr);
env.last_result = r;
sr_code(r)
}
/// Whether fallback-histogram stats are enabled (`XENIA_JIT_STATS`), cached.
fn stats_enabled() -> bool {
use std::sync::OnceLock;
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| std::env::var("XENIA_JIT_STATS").is_ok())
}
fn fallback_hist() -> &'static std::sync::Mutex<std::collections::HashMap<xenia_cpu::PpcOpcode, u64>> {
use std::sync::OnceLock;
static H: OnceLock<std::sync::Mutex<std::collections::HashMap<xenia_cpu::PpcOpcode, u64>>> =
OnceLock::new();
H.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
}
/// Print the fallback-opcode histogram (top 30) to stderr. No-op unless
/// `XENIA_JIT_STATS` is set. Call once after a run to see which un-ported
/// opcodes dominate the interpreter-fallback tax.
pub fn dump_fallback_stats() {
if !stats_enabled() {
return;
}
let h = fallback_hist().lock().unwrap();
let mut v: Vec<(xenia_cpu::PpcOpcode, u64)> = h.iter().map(|(k, c)| (*k, *c)).collect();
v.sort_by_key(|(_, c)| std::cmp::Reverse(*c));
let total: u64 = v.iter().map(|(_, c)| *c).sum();
eprintln!("=== JIT fallback histogram (total fallback executions: {total}) ===");
for (op, c) in v.iter().take(30) {
let pct = if total > 0 { 100.0 * (*c as f64) / (total as f64) } else { 0.0 };
eprintln!(" {:>14?} {:>14} ({pct:>5.1}%)", op, c);
}
}
// ---- memory-access helpers called from emitted load/store code ----
//
// Each reconstructs `&dyn MemoryAccess` (and, for stores, `&PpcContext`) from