diff --git a/crates/xenia-cpu/src/jit.rs b/crates/xenia-cpu/src/jit.rs index 6d634bd..6365a55 100644 --- a/crates/xenia-cpu/src/jit.rs +++ b/crates/xenia-cpu/src/jit.rs @@ -212,6 +212,14 @@ pub fn covered(instr: &DecodedInstr) -> bool { // (sign-extending) loads are not lowered yet. lbz | lbzx | lhz | lhzx | lwz | lwzx | ld | ldx => true, stb | stbx | sth | sthx | stw | stwx | std | stdx => true, + // FP loads/stores are pure moves (no fpscr). Single-precision forms + // widen/narrow via IEEE round-to-nearest, matching the interpreter. + lfs | lfsx | lfd | lfdx | stfs | stfsx | stfd | stfdx => true, + // FP register moves: only the Rc=0 forms (the `.` forms update CR1 from + // fpscr). fmr/fabs/fneg are sign-bit ops with no fpscr side effect. + // 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(), // Branch terminators. bx | bcx | bclrx => true, _ => false, @@ -391,6 +399,11 @@ fn gpr_off(r: usize) -> i32 { (offset_of!(PpcContext, gpr) + r * 8) as i32 } +#[inline] +fn fpr_off(r: usize) -> i32 { + (offset_of!(PpcContext, fpr) + r * 8) as i32 +} + #[inline] fn load_gpr(b: &mut FunctionBuilder, ctxp: Value, r: usize) -> Value { b.ins().load(types::I64, MemFlags::trusted(), ctxp, gpr_off(r)) @@ -909,6 +922,78 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) { call_store(b, ec, ec.tr.write64, ea, v); } + // ---- FP loads. lfs/lfsx: load single, widen to the f64 FPR. ---- + PpcOpcode::lfs => { + let ea = ea_d(b, ec, instr.ra(), instr.d()); + let bits = call_load(b, ec, ec.tr.read32, ea); + let f32v = b.ins().bitcast(types::F32, MemFlags::new(), bits); + let f64v = b.ins().fpromote(types::F64, f32v); + b.ins().store(MemFlags::trusted(), f64v, ctxp, fpr_off(instr.rd())); + } + PpcOpcode::lfsx => { + let ea = ea_x(b, ec, instr.ra(), instr.rb()); + let bits = call_load(b, ec, ec.tr.read32, ea); + let f32v = b.ins().bitcast(types::F32, MemFlags::new(), bits); + let f64v = b.ins().fpromote(types::F64, f32v); + b.ins().store(MemFlags::trusted(), f64v, ctxp, fpr_off(instr.rd())); + } + // lfd/lfdx: 64-bit — a pure bit copy into the FPR. + PpcOpcode::lfd => { + let ea = ea_d(b, ec, instr.ra(), instr.d()); + let bits = call_load(b, ec, ec.tr.read64, ea); + b.ins().store(MemFlags::trusted(), bits, ctxp, fpr_off(instr.rd())); + } + PpcOpcode::lfdx => { + let ea = ea_x(b, ec, instr.ra(), instr.rb()); + let bits = call_load(b, ec, ec.tr.read64, ea); + b.ins().store(MemFlags::trusted(), bits, ctxp, fpr_off(instr.rd())); + } + + // ---- FP stores. stfs/stfsx: narrow the f64 FPR to single. ---- + PpcOpcode::stfs => { + let ea = ea_d(b, ec, instr.ra(), instr.d()); + let f64v = b.ins().load(types::F64, MemFlags::trusted(), ctxp, fpr_off(instr.rs())); + let f32v = b.ins().fdemote(types::F32, f64v); + let bits = b.ins().bitcast(types::I32, MemFlags::new(), f32v); + call_store(b, ec, ec.tr.write32, ea, bits); + } + PpcOpcode::stfsx => { + let ea = ea_x(b, ec, instr.ra(), instr.rb()); + let f64v = b.ins().load(types::F64, MemFlags::trusted(), ctxp, fpr_off(instr.rs())); + let f32v = b.ins().fdemote(types::F32, f64v); + let bits = b.ins().bitcast(types::I32, MemFlags::new(), f32v); + call_store(b, ec, ec.tr.write32, ea, bits); + } + // stfd/stfdx: 64-bit — a pure bit copy out of the FPR. + PpcOpcode::stfd => { + let ea = ea_d(b, ec, instr.ra(), instr.d()); + let bits = b.ins().load(types::I64, MemFlags::trusted(), ctxp, fpr_off(instr.rs())); + call_store(b, ec, ec.tr.write64, ea, bits); + } + PpcOpcode::stfdx => { + let ea = ea_x(b, ec, instr.ra(), instr.rb()); + let bits = b.ins().load(types::I64, MemFlags::trusted(), ctxp, fpr_off(instr.rs())); + call_store(b, ec, ec.tr.write64, ea, bits); + } + + // ---- FP register moves (Rc=0; the `.` forms read fpscr → uncovered). + // Done as bit ops on the 64-bit pattern so they exactly match Rust's + // f64 copy / sign-bit abs / sign-bit negate. ---- + PpcOpcode::fmrx => { + let bits = b.ins().load(types::I64, MemFlags::trusted(), ctxp, fpr_off(instr.rb())); + b.ins().store(MemFlags::trusted(), bits, ctxp, fpr_off(instr.rd())); + } + PpcOpcode::fabsx => { + let bits = b.ins().load(types::I64, MemFlags::trusted(), ctxp, fpr_off(instr.rb())); + let res = b.ins().band_imm(bits, i64::MAX); // clear sign bit (bit 63) + b.ins().store(MemFlags::trusted(), res, ctxp, fpr_off(instr.rd())); + } + PpcOpcode::fnegx => { + let bits = b.ins().load(types::I64, MemFlags::trusted(), ctxp, fpr_off(instr.rb())); + let res = b.ins().bxor_imm(bits, i64::MIN); // flip sign bit (bit 63) + b.ins().store(MemFlags::trusted(), res, ctxp, fpr_off(instr.rd())); + } + // Unconditional branch. Target is an immediate; `pc` (= this instr's // address in the interpreter) is `instr.addr` at emit time. PpcOpcode::bx => {