[iterate-4A] jit: wire compiled-block cache + cover bx/bcx/bclrx (diff-clean, 6.57% native)
Wire the Cranelift block-JIT into execution and add branch coverage. Both
increments validated bit-exact against the interpreter via the in-process
differential harness on a full boot+movie run (movie plays, clean exit):
wiring only (addi/addis): checked 146.8M blocks, 0.01% native, MISMATCHES=0
+ branches (bx/bcx/bclrx): checked 148.5M blocks, 6.57% native (9.76M), MISMATCHES=0
jit.rs
* JitCache: direct-mapped 64K-slot compiled-block cache keyed (start_pc,
page_version) identically to BlockCache, so self-modifying / DMA'd code
invalidates native code the same way. Caches the None ("uncovered") verdict
so an uncovered block is compile-attempted at most once per (pc,version).
Owns the Jit/JITModule (keeps every CompiledFn valid for its lifetime).
* covered(): addi/addis + bx/bcx/bclrx. bcctrx excluded (indirect target +
dispatch_rec diagnostic hook the native path would skip).
* pc-handling refactor: a branch terminator writes pc itself (writes_pc());
the block epilogue stores end_pc only for straight-line (max-len / page-
boundary) blocks. cycle/timebase still += N (covered ops never fault/yield;
a branch is always the last instruction).
* Branch lowering uses immediate targets — the interpreter's ctx.pc equals the
instruction address at emit time, so bx/bcx relative targets are constants.
emit_branch_taken mirrors the interpreter: optional CTR decrement, ctr_ok =
(ctr as u32 vs 0) inverted by BO3, cond_ok = CR-bit BI byte == BO1 (both BO
sub-cases const-fold), combined with select. bclrx reads lr&!3 before the LK
link overwrites lr.
recompiler.rs
* run_block / diff_step take &mut JitCache. run_block runs the native fn when
get_or_compile returns one (else interpreter fallback); diff_step runs it on
the speculative clone/OverlayMemory so every native block is diff-checked.
* report_jit_summary(): native-vs-interpreted block counts (XENIA_JIT / _DIFF).
main.rs
* WorkerCtx owns a JitCache; run_superblock routing passes it to
diff_step/run_block; report_jit_summary() at clean exit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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::jit::{JitCache, MemEnv, RET_CONTINUE};
|
||||
use crate::opcode::PpcOpcode;
|
||||
use xenia_memory::MemoryAccess;
|
||||
|
||||
@@ -127,16 +128,38 @@ pub fn report_histogram() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute one decoded block. **M0: interpreter fallback for every opcode.**
|
||||
// Coverage telemetry: blocks executed as native code vs interpreted (uncovered).
|
||||
static JIT_COMPILED_RUN: AtomicU64 = AtomicU64::new(0);
|
||||
static JIT_INTERP_RUN: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Execute one decoded block, natively if it is fully covered, else via the
|
||||
/// interpreter fallback.
|
||||
///
|
||||
/// Byte-for-byte the same loop as [`crate::interpreter::step_block`]: bump
|
||||
/// `cycle_count`/`timebase` per instruction, bail on the first non-`Continue`
|
||||
/// result, and stop on a PC discontinuity (only the terminator may branch).
|
||||
/// If [`JitCache::get_or_compile`] returns a compiled entry point, the whole
|
||||
/// block runs as native code (which advances `pc`/`cycle_count`/`timebase`
|
||||
/// exactly like the interpreter — validated by the diff harness). Otherwise
|
||||
/// this is byte-for-byte the same loop as [`crate::interpreter::step_block`]:
|
||||
/// bump `cycle_count`/`timebase` per instruction, bail on the first
|
||||
/// non-`Continue` result, and stop on a PC discontinuity (only the terminator
|
||||
/// may branch).
|
||||
pub fn run_block(
|
||||
ctx: &mut PpcContext,
|
||||
mem: &dyn MemoryAccess,
|
||||
block: &DecodedBlock,
|
||||
jit: &mut JitCache,
|
||||
) -> StepResult {
|
||||
if let Some(f) = jit.get_or_compile(block) {
|
||||
JIT_COMPILED_RUN.fetch_add(1, Ordering::Relaxed);
|
||||
let env = MemEnv { mem };
|
||||
let raw = f(ctx as *mut PpcContext, &env as *const MemEnv);
|
||||
// Covered blocks are straight-line (branch/sc/trap/db16cyc are all
|
||||
// uncovered), so a compiled block always runs to completion and
|
||||
// returns `Continue`.
|
||||
debug_assert_eq!(raw, RET_CONTINUE, "covered block returned non-Continue");
|
||||
return StepResult::Continue;
|
||||
}
|
||||
JIT_INTERP_RUN.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let hist = hist_enabled();
|
||||
let mut result = StepResult::Continue;
|
||||
for instr in &block.instrs {
|
||||
@@ -174,6 +197,7 @@ pub fn diff_step(
|
||||
ctx: &mut PpcContext,
|
||||
mem: &dyn MemoryAccess,
|
||||
block: &DecodedBlock,
|
||||
jit: &mut JitCache,
|
||||
) -> StepResult {
|
||||
// Reservation/barrier blocks share cross-thread state; don't speculate.
|
||||
if block.sync_sensitive {
|
||||
@@ -186,7 +210,7 @@ pub fn diff_step(
|
||||
let mut cand = ctx.clone();
|
||||
cand.reservation_table = None; // never touch the shared reservation table
|
||||
let overlay = OverlayMemory::new(mem);
|
||||
let _ = run_block(&mut cand, &overlay, block);
|
||||
let _ = run_block(&mut cand, &overlay, block, jit);
|
||||
let touched_mmio = overlay.touched_mmio.get();
|
||||
|
||||
// Authoritative interpreter run: commits real ctx + memory.
|
||||
@@ -271,6 +295,22 @@ pub fn report_diff_summary() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Print native-vs-interpreted block coverage (call at clean exit). Shown
|
||||
/// whenever the JIT is engaged (`XENIA_JIT` or `XENIA_JIT_DIFF`) so we can see
|
||||
/// how much of the real boot+movie workload the current covered set captures.
|
||||
pub fn report_jit_summary() {
|
||||
if !jit_enabled() && !diff_enabled() {
|
||||
return;
|
||||
}
|
||||
let compiled = JIT_COMPILED_RUN.load(Ordering::Relaxed);
|
||||
let interp = JIT_INTERP_RUN.load(Ordering::Relaxed);
|
||||
let total = compiled + interp;
|
||||
let pct = if total == 0 { 0.0 } else { 100.0 * compiled as f64 / total as f64 };
|
||||
eprintln!(
|
||||
"=== JIT COVERAGE: native blocks={compiled} interpreted={interp} ({pct:.2}% of {total} block-runs native) ==="
|
||||
);
|
||||
}
|
||||
|
||||
// ---- overlay memory (speculative-write buffer) ---------------------------
|
||||
|
||||
/// A [`MemoryAccess`] wrapper for speculative JIT execution: reads fall through
|
||||
|
||||
Reference in New Issue
Block a user