From 0becc9185afc318900e5a3ec6b59bed8bac937aa Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Wed, 8 Jul 2026 06:33:01 +0200 Subject: [PATCH] =?UTF-8?q?[iterate-4A]=20jit:=20coverage=20=E2=80=94=20ch?= =?UTF-8?q?eap=20integer=20ALU=20ops=20(+5pt=20native,=20chain=20-14%=20vs?= =?UTF-8?q?=20interp)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Data-driven from XENIA_JIT_HIST: after register caching the JIT was 73% native; the frequent uncovered opcodes are VMX128 (the long pole), mtspr/mfspr, update- form loads/stores, and a batch of cheap pure-register integer ops. This does the cheap integer batch (each unblocks any block that only lacked it, and — since a covered block can now chain through where an uncovered op used to break the chain — the gain compounds: more native execution AND longer superblocks). Added to covered() + emit_op, all validated against the interpreter by the diff harness (none are sync_sensitive, so fully covered): - extsbx/extshx/extswx — sign-extend byte/half/word (extsb/extsh write the i32-view zero-extended per the 32-bit ABI, CR0 i32; extsw sign-extends into the full 64, CR0 i64). - cntlzwx/cntlzdx — count leading zeros (Cranelift clz). - negx (OE=0) — rD = 0 - rA (ineg), full 64-bit, CR0 on low 32. - slwx/srwx — word shifts; explicit `sh<32 ? shift : 0` select because the count is rB[58:63] (6 bits) and Cranelift's ishl/ushr mask to 5. - sldx/srdx — doubleword shifts, `sh<64 ? shift : 0` (count 7 bits). New emit_cr0_signed64 for the doubleword-result CR0 (a value with low32==0 but high bits set is eq in the 32-bit view but not the 64-bit one). The carry-setting shifts (sraw*/srad*, set XER-CA) and rotate-double (rldic*) are deferred to later batches. Validated (foreground per the bg-SIGTERM finding): 7/7 jit unit tests; diff MISMATCHES=0 over 47.9M blocks; coverage 75.4% -> 80.6% (single-block, diff mode). Perf (RUST_LOG=warn, -n 3e9 to amortize compiles): chain wall 57952ms -> 54748ms (-5.5% from coverage alone; run_block calls 110M -> 98M = more chaining), chain -14.3% vs interp (63.9s) this session. -n 2e9 understates it (more blocks compiled = more compile cost inside the STEP timer at under-amortized -n). Co-Authored-By: Claude Opus 4.8 --- crates/xenia-cpu/src/jit.rs | 138 ++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) 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() {