From afc3692223b9043591870e5dc453b363c6b6b237 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 4 Jul 2026 20:14:46 +0200 Subject: [PATCH] [iterate-4C] JIT: gated fallback-opcode histogram (XENIA_JIT_STATS) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- crates/xenia-app/src/main.rs | 4 ++++ crates/xenia-jit/src/lib.rs | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/crates/xenia-app/src/main.rs b/crates/xenia-app/src/main.rs index f42eb7f..e569aaa 100644 --- a/crates/xenia-app/src/main.rs +++ b/crates/xenia-app/src/main.rs @@ -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(()) })() }; diff --git a/crates/xenia-jit/src/lib.rs b/crates/xenia-jit/src/lib.rs index caaee6e..b439cc3 100644 --- a/crates/xenia-jit/src/lib.rs +++ b/crates/xenia-jit/src/lib.rs @@ -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 = OnceLock::new(); + *ON.get_or_init(|| std::env::var("XENIA_JIT_STATS").is_ok()) +} + +fn fallback_hist() -> &'static std::sync::Mutex> { + use std::sync::OnceLock; + static H: OnceLock>> = + 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