[iterate-4A] jit: coverage — cheap integer ALU ops (+5pt native, chain -14% vs interp)

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 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-08 06:33:01 +02:00
parent e3cc6d11d0
commit 0becc9185a

View File

@@ -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() {