diff --git a/crates/xenia-cpu/src/jit.rs b/crates/xenia-cpu/src/jit.rs index 6365a55..4ba349d 100644 --- a/crates/xenia-cpu/src/jit.rs +++ b/crates/xenia-cpu/src/jit.rs @@ -100,6 +100,22 @@ extern "C" fn xj_write64(ctxp: *const PpcContext, env: *const MemEnv, addr: u32, unsafe { (*env).mem.write_u64(addr, val) } } +/// Generic single-instruction interpreter shim ("punt"). Decodes `raw` at +/// `addr` and runs it through the interpreter's [`crate::interpreter::execute`], +/// mutating the live context/memory in place. Lets a block containing an op we +/// don't lower natively still be JIT-compiled: the surrounding covered ops run +/// as machine code and only the punted op calls back into Rust — bit-exact by +/// construction. Used for FP arithmetic (whose `fpscr` bookkeeping is too +/// intricate to lower in IR). Only ops that are **always `Continue` and never +/// branch** may be punted (`execute` advances `pc` by 4; the block epilogue then +/// stamps `end_pc` for non-branch blocks, so intermediate `pc` doesn't matter). +extern "C" fn xj_interp_op(ctxp: *mut PpcContext, memenv: *const MemEnv, raw: u32, addr: u32) { + let ctx = unsafe { &mut *ctxp }; + let mem = unsafe { (*memenv).mem }; + let instr = crate::decoder::decode(raw, addr); + let _ = crate::interpreter::execute(ctx, mem, &instr); +} + /// `FuncRef`s for the eight trampolines, declared into the function being built. /// Copied into each block's IR so load/store arms can emit calls. #[derive(Clone, Copy)] @@ -112,6 +128,7 @@ struct Trampolines { write16: FuncRef, write32: FuncRef, write64: FuncRef, + interp: FuncRef, } /// Per-compile emit context: the two entry-block pointer params plus the @@ -220,6 +237,8 @@ pub fn covered(instr: &DecodedInstr) -> bool { // FP *arithmetic* (fadds/fmuls/fmadds…) is NOT covered — it updates // fpscr (FPRF/FI/FR, invalid-op flags), which is not lowered yet. fmrx | fabsx | fnegx => !instr.rc_bit(), + // FP arithmetic/compare — lowered by punting to the interpreter. + op if is_fp_punt(op) => true, // Branch terminators. bx | bcx | bclrx => true, _ => false, @@ -232,6 +251,21 @@ fn writes_pc(instr: &DecodedInstr) -> bool { matches!(instr.opcode, PpcOpcode::bx | PpcOpcode::bcx | PpcOpcode::bclrx) } +/// FP ops lowered by punting to the interpreter ([`xj_interp_op`]) rather than +/// native IR: their `fpscr` bookkeeping (rounding-mode-dependent rounding, +/// sticky exception bits, FPRF classification) is too intricate to emit in IR, +/// but punting lets a block containing them still compile (surrounding covered +/// ops run native). All are always-`Continue`, non-branch ops. +fn is_fp_punt(op: PpcOpcode) -> bool { + use PpcOpcode::*; + matches!( + op, + faddx | faddsx | fsubx | fsubsx | fmulx | fmulsx | fdivx | fdivsx + | fmaddx | fmaddsx | fmsubx | fmsubsx | fnmaddx | fnmaddsx | fnmsubx | fnmsubsx + | frspx | fsqrtx | fresx | frsqrtex | fselx | fnabsx | fcmpu | fcmpo + ) +} + /// True if the whole block can be compiled (all opcodes covered). pub fn block_covered(block: &DecodedBlock) -> bool { block.instrs.iter().all(covered) @@ -258,6 +292,7 @@ struct TrampIds { write16: FuncId, write32: FuncId, write64: FuncId, + interp: FuncId, } impl Jit { @@ -274,6 +309,7 @@ impl Jit { builder.symbol("xj_write16", xj_write16 as *const u8); builder.symbol("xj_write32", xj_write32 as *const u8); builder.symbol("xj_write64", xj_write64 as *const u8); + builder.symbol("xj_interp_op", xj_interp_op as *const u8); let mut module = JITModule::new(builder); let ptr = module.target_config().pointer_type(); @@ -307,6 +343,15 @@ impl Jit { write16: declare("xj_write16", &store_sig(types::I32))?, write32: declare("xj_write32", &store_sig(types::I32))?, write64: declare("xj_write64", &store_sig(types::I64))?, + interp: { + // interp(ctx: ptr, env: ptr, raw: i32, addr: i32) + let mut s = Signature::new(cc); + s.params.push(AbiParam::new(ptr)); + s.params.push(AbiParam::new(ptr)); + s.params.push(AbiParam::new(types::I32)); + s.params.push(AbiParam::new(types::I32)); + declare("xj_interp_op", &s)? + }, }; let ctx = module.make_context(); @@ -337,6 +382,7 @@ impl Jit { write16: self.module.declare_func_in_func(self.tramp_ids.write16, &mut self.ctx.func), write32: self.module.declare_func_in_func(self.tramp_ids.write32, &mut self.ctx.func), write64: self.module.declare_func_in_func(self.tramp_ids.write64, &mut self.ctx.func), + interp: self.module.declare_func_in_func(self.tramp_ids.interp, &mut self.ctx.func), }; { @@ -1047,6 +1093,13 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) { store_lr(b, ctxp, next as u64); } } + // FP arithmetic/compare: punt to the interpreter via xj_interp_op. + // execute() runs the exact op (result + fpscr) on the live ctx/mem. + op if is_fp_punt(op) => { + let raw = b.ins().iconst(types::I32, instr.raw as i64); + let addr = b.ins().iconst(types::I32, instr.addr as i64); + b.ins().call(ec.tr.interp, &[ec.ctxp, ec.memenv, raw, addr]); + } // covered() gates this — unreachable for uncovered opcodes. _ => unreachable!("emit_op called on uncovered opcode {:?}", instr.opcode), }