[iterate-4A] jit: opcode-frequency histogram (XENIA_JIT_HIST) for coverage targeting

Gated 1-in-16 sampled opcode histogram in the recompiler (tallied in run_block,
printed sorted with cumulative % at clean exit). Drives which opcodes the JIT
lowers first. Boot+movie result: workload is FP-heavy — addi 15%, lwz 12%,
rlwinm 10%, lfs 9%, stfs 7.5%, lfsx 5%, fmaddsx 4.6%, bc 3.7%, fmulsx 3.6%,
stw 3.4% ... top-20 = 90%, ~33% floating-point. This is why we go straight to a
Cranelift machine-code JIT (which attacks the FP op bodies) rather than a
closure-threaded stage (which only removes dispatch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-06 20:36:53 +02:00
parent ac289bca5e
commit 07cd272412
2 changed files with 59 additions and 0 deletions

View File

@@ -4396,6 +4396,8 @@ fn dump_thread_diagnostic(
}
// JIT differential harness: checked/skipped/mismatch tally at clean exit.
xenia_cpu::recompiler::report_diff_summary();
// JIT opcode-frequency histogram (XENIA_JIT_HIST) — drives M1 coverage.
xenia_cpu::recompiler::report_histogram();
// STEP-10 diagnostic (observe-only, env-gated `XENIA_DUMP_SLOTS=1`).
// Prints each scheduler slot's full runqueue with the fields needed to

View File

@@ -43,6 +43,7 @@ use std::sync::atomic::{AtomicU64, AtomicU8, Ordering};
use crate::block_cache::DecodedBlock;
use crate::context::PpcContext;
use crate::interpreter::{execute, step_block, StepResult};
use crate::opcode::PpcOpcode;
use xenia_memory::MemoryAccess;
/// Cached env gate. 0 = uninitialised, 1 = on, 2 = off.
@@ -74,6 +75,58 @@ pub fn diff_enabled() -> bool {
cached_flag(&F, "XENIA_JIT_DIFF")
}
// ---- opcode histogram (XENIA_JIT_HIST) — data-drives M1 coverage ---------
#[inline]
fn hist_enabled() -> bool {
static F: AtomicU8 = AtomicU8::new(0);
cached_flag(&F, "XENIA_JIT_HIST")
}
thread_local! {
static HIST: RefCell<HashMap<PpcOpcode, u64>> = RefCell::new(HashMap::new());
static HIST_TICK: Cell<u32> = const { Cell::new(0) };
}
/// Sample 1-in-16 instructions — relative opcode frequencies are unbiased at
/// this rate and the HashMap cost stops dominating the run.
#[inline]
fn hist_tally(op: PpcOpcode) {
let t = HIST_TICK.with(|c| {
let n = c.get().wrapping_add(1);
c.set(n);
n
});
if t & 0xF == 0 {
HIST.with(|h| *h.borrow_mut().entry(op).or_insert(0) += 1);
}
}
/// Print the opcode-frequency histogram (top 40 + cumulative %), so M1 coverage
/// targets the opcodes that actually dominate boot+movie execution.
pub fn report_histogram() {
if !hist_enabled() {
return;
}
let mut v: Vec<(PpcOpcode, u64)> =
HIST.with(|h| h.borrow().iter().map(|(&k, &c)| (k, c)).collect());
v.sort_by(|a, b| b.1.cmp(&a.1));
let total: u64 = v.iter().map(|(_, c)| c).sum();
eprintln!("=== JIT opcode histogram (total={total}, {} distinct) ===", v.len());
let mut cum = 0u64;
for (i, (op, c)) in v.iter().take(40).enumerate() {
cum += c;
eprintln!(
" {:>2}. {:<14?} {:>12} {:>5.1}% cum {:>5.1}%",
i + 1,
op,
c,
100.0 * *c as f64 / total as f64,
100.0 * cum as f64 / total as f64
);
}
}
/// Execute one decoded block. **M0: interpreter fallback for every opcode.**
///
/// Byte-for-byte the same loop as [`crate::interpreter::step_block`]: bump
@@ -84,9 +137,13 @@ pub fn run_block(
mem: &dyn MemoryAccess,
block: &DecodedBlock,
) -> StepResult {
let hist = hist_enabled();
let mut result = StepResult::Continue;
for instr in &block.instrs {
let expected_next = instr.addr.wrapping_add(4);
if hist {
hist_tally(instr.opcode);
}
// M0 fallback: identical to the interpreter. Future stages dispatch
// lowered ops here and only fall back for uncompiled opcodes.
result = execute(ctx, mem, instr);