diff --git a/crates/xenia-cpu/src/jit.rs b/crates/xenia-cpu/src/jit.rs index 767d2e1..74f2355 100644 --- a/crates/xenia-cpu/src/jit.rs +++ b/crates/xenia-cpu/src/jit.rs @@ -412,6 +412,12 @@ pub fn covered(instr: &DecodedInstr) -> bool { ori | oris | xori | xoris | andix | andisx => true, addx | subfx => !instr.oe(), rlwinmx => true, + // Sign-extend, count-leading-zeros, negate (OE=0), and the non-carry + // shifts. All pure register ops with no XER side effect (the carry + // shifts sraw*/srad* set XER-CA and are not lowered yet). + extsbx | extshx | extswx | cntlzwx | cntlzdx => true, + negx => !instr.oe(), + slwx | srwx | sldx | srdx => true, cmp | cmpl | cmpi | cmpli => true, // Integer loads/stores via memory trampolines (zero-extended loads and // the non-update forms only). Update (`u`) forms and algebraic @@ -1121,6 +1127,17 @@ fn emit_cr0_signed32(b: &mut FunctionBuilder, ctxp: Value, val32: Value) { emit_store_cr(b, ctxp, 0, lt, gt, eq); } +/// Emit `update_cr_signed(0, val as i64)` — the Rc-form CR0 update for a full +/// 64-bit result (doubleword ops: `sldx`/`srdx`/`cntlzdx`/`extswx`). A value +/// whose low 32 bits are zero but high bits are set is `eq` in the 32-bit view +/// but not the 64-bit one, so these must compare the whole register. +fn emit_cr0_signed64(b: &mut FunctionBuilder, ctxp: Value, val64: Value) { + let lt = b.ins().icmp_imm(IntCC::SignedLessThan, val64, 0); + let gt = b.ins().icmp_imm(IntCC::SignedGreaterThan, val64, 0); + let eq = b.ins().icmp_imm(IntCC::Equal, val64, 0); + emit_store_cr(b, ctxp, 0, lt, gt, eq); +} + /// Load the single CR bit `bi` (0-31) as an `I8` boolean (0/1). CR fields are /// `#[repr] struct CrField { lt, gt, eq, so }` — one byte each — so bit /// `bi = field*4 + sub` is the byte at `cr + field*4 + sub`. @@ -1536,6 +1553,127 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) { } } + // ---- sign-extend: low byte/half/word of rS → 64-bit rA. ---- + // extsb/extsh write the (sign-extended-to-32) value zero-extended into + // the 64-bit GPR (32-bit ABI); CR0 is the i32 view. extsw sign-extends + // the low 32 into the full 64, CR0 the i64 view. + PpcOpcode::extsbx => { + let rs = gpr32(b, ec, instr.rs()); + let byte = b.ins().ireduce(types::I8, rs); + let s32 = b.ins().sextend(types::I32, byte); + store_gpr32z(b, ec, instr.ra(), s32); + if instr.rc_bit() { + emit_cr0_signed32(b, ctxp, s32); + } + } + PpcOpcode::extshx => { + let rs = gpr32(b, ec, instr.rs()); + let half = b.ins().ireduce(types::I16, rs); + let s32 = b.ins().sextend(types::I32, half); + store_gpr32z(b, ec, instr.ra(), s32); + if instr.rc_bit() { + emit_cr0_signed32(b, ctxp, s32); + } + } + PpcOpcode::extswx => { + let rs = cache_read_gpr(b, ec, instr.rs()); + let low = b.ins().ireduce(types::I32, rs); + let s64 = b.ins().sextend(types::I64, low); + cache_write_gpr(ec, instr.ra(), s64); + if instr.rc_bit() { + emit_cr0_signed64(b, ctxp, s64); + } + } + + // ---- count leading zeros (word: 0..=32, dword: 0..=64). ---- + PpcOpcode::cntlzwx => { + let rs = gpr32(b, ec, instr.rs()); + let clz = b.ins().clz(rs); + store_gpr32z(b, ec, instr.ra(), clz); + if instr.rc_bit() { + emit_cr0_signed32(b, ctxp, clz); + } + } + PpcOpcode::cntlzdx => { + let rs = cache_read_gpr(b, ec, instr.rs()); + let clz = b.ins().clz(rs); + cache_write_gpr(ec, instr.ra(), clz); + if instr.rc_bit() { + emit_cr0_signed64(b, ctxp, clz); + } + } + + // ---- negate (OE=0): rD = 0 - rA, full 64-bit; CR0 on low 32. ---- + PpcOpcode::negx => { + let ra = cache_read_gpr(b, ec, instr.ra()); + let res = b.ins().ineg(ra); + cache_write_gpr(ec, instr.rd(), res); + if instr.rc_bit() { + let lo = b.ins().ireduce(types::I32, res); + emit_cr0_signed32(b, ctxp, lo); + } + } + + // ---- word shifts (no XER-CA). rA = sh<32 ? rS<<|>>sh : 0, zero-ext. + // Shift count is rB[58:63] (6 bits): a count 32..63 zeroes the result + // (bit-5 set), so an explicit `sh < 32` select is required — Cranelift's + // ishl/ushr mask the count to 5 bits and would wrap instead. ---- + PpcOpcode::slwx => { + let rs = gpr32(b, ec, instr.rs()); + let rb = gpr32(b, ec, instr.rb()); + let sh = b.ins().band_imm(rb, 0x3F); + let shifted = b.ins().ishl(rs, sh); + let cond = b.ins().icmp_imm(IntCC::UnsignedLessThan, sh, 32); + let zero = b.ins().iconst(types::I32, 0); + let res = b.ins().select(cond, shifted, zero); + store_gpr32z(b, ec, instr.ra(), res); + if instr.rc_bit() { + emit_cr0_signed32(b, ctxp, res); + } + } + PpcOpcode::srwx => { + let rs = gpr32(b, ec, instr.rs()); + let rb = gpr32(b, ec, instr.rb()); + let sh = b.ins().band_imm(rb, 0x3F); + let shifted = b.ins().ushr(rs, sh); + let cond = b.ins().icmp_imm(IntCC::UnsignedLessThan, sh, 32); + let zero = b.ins().iconst(types::I32, 0); + let res = b.ins().select(cond, shifted, zero); + store_gpr32z(b, ec, instr.ra(), res); + if instr.rc_bit() { + emit_cr0_signed32(b, ctxp, res); + } + } + + // ---- doubleword shifts (no XER-CA). rA = sh<64 ? rS<<|>>sh : 0. + // Count is rB[57:63] (7 bits); same wrap concern as the word forms. ---- + PpcOpcode::sldx => { + let rs = cache_read_gpr(b, ec, instr.rs()); + let rb = cache_read_gpr(b, ec, instr.rb()); + let sh = b.ins().band_imm(rb, 0x7F); + let shifted = b.ins().ishl(rs, sh); + let cond = b.ins().icmp_imm(IntCC::UnsignedLessThan, sh, 64); + let zero = b.ins().iconst(types::I64, 0); + let res = b.ins().select(cond, shifted, zero); + cache_write_gpr(ec, instr.ra(), res); + if instr.rc_bit() { + emit_cr0_signed64(b, ctxp, res); + } + } + PpcOpcode::srdx => { + let rs = cache_read_gpr(b, ec, instr.rs()); + let rb = cache_read_gpr(b, ec, instr.rb()); + let sh = b.ins().band_imm(rb, 0x7F); + let shifted = b.ins().ushr(rs, sh); + let cond = b.ins().icmp_imm(IntCC::UnsignedLessThan, sh, 64); + let zero = b.ins().iconst(types::I64, 0); + let res = b.ins().select(cond, shifted, zero); + cache_write_gpr(ec, instr.ra(), res); + if instr.rc_bit() { + emit_cr0_signed64(b, ctxp, res); + } + } + // ---- compares. Write CR field crfd() unconditionally. ---- PpcOpcode::cmp => { let (lt, gt, eq) = if instr.l() {