[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:
@@ -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(())
|
||||
})()
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user