[iterate-4A] jit: coverage — update-form loads/stores + mfspr/mtspr(LR/CTR) → 89.6% native
Second coverage batch, data-driven from the histogram. Diff harness validates every op against the interpreter (MISMATCHES=0 over 47.96M blocks). Update-form load/stores (24 ops: lbzu..stdux, lfsu..stfdux and their x-forms): same access as the base form but with EA = gpr[ra] + offset (ra used directly — update forms are illegal with ra==0) and rA := EA written back (zero-extended) after the access. New ea_d_update/ea_x_update/write_ea_back helpers; loads write rD then rA (so rA wins if rd==ra, matching the interpreter). stwu especially is the standard stack-frame push in every function prologue. mfspr/mtspr for LR and CTR only (64-bit field copies): the ubiquitous mflr/mtlr prologue-epilogue pair and mtctr. covered() gates on the compile-time SPR number so the offset always resolves; XER (packs CA/OV/SO) and the modelled SPRs stay uncovered. This is the biggest single jump — mflr/mtlr gate almost every non-leaf function's prologue and epilogue blocks. Coverage (single-block, diff mode): 80.6% -> 82.8% (update forms) -> 89.6% (mfspr/mtspr). 7/7 jit unit tests; foreground-validated per the bg-SIGTERM finding. Carry-setting shifts (sraw*/srad*), rotate-double (rldic*), and XER mfspr/mtspr remain for later batches; VMX128 is the long pole. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -424,9 +424,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,
|
||||
// Update (`u`) forms: base load/store + write the EA back to rA (zero-
|
||||
// extended). Algebraic (sign-extending) loads remain uncovered.
|
||||
lbzu | lbzux | lhzu | lhzux | lwzu | lwzux | ldu | ldux => true,
|
||||
stbu | stbux | sthu | sthux | stwu | stwux | stdu | stdux => 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,
|
||||
lfsu | lfsux | lfdu | lfdux | stfsu | stfsux | stfdu | stfdux => 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
|
||||
@@ -434,6 +439,13 @@ pub fn covered(instr: &DecodedInstr) -> bool {
|
||||
fmrx | fabsx | fnegx => !instr.rc_bit(),
|
||||
// FP arithmetic/compare — lowered by punting to the interpreter.
|
||||
op if is_fp_punt(op) => true,
|
||||
// mfspr/mtspr for LR and CTR only — trivial 64-bit field copies (the
|
||||
// ubiquitous mflr/mtlr prologue pair and mtctr). XER (packs the CA/OV/SO
|
||||
// flags) and the modelled SPRs (timebase, DEC, VRSAVE, …) stay uncovered.
|
||||
mfspr | mtspr => matches!(
|
||||
instr.spr(),
|
||||
crate::context::spr::LR | crate::context::spr::CTR
|
||||
),
|
||||
// Branch terminators.
|
||||
bx | bcx | bclrx => true,
|
||||
_ => false,
|
||||
@@ -1247,6 +1259,31 @@ fn ea_x(b: &mut FunctionBuilder, ec: &EmitCtx, ra: usize, rb: usize) -> Value {
|
||||
b.ins().ireduce(types::I32, ea64)
|
||||
}
|
||||
|
||||
/// EA for an **update-form** D load/store: `gpr[ra] + disp` truncated to 32
|
||||
/// bits. Unlike [`ea_d`], `ra` is used directly (update forms are illegal with
|
||||
/// `ra==0`). The returned EA is also written back to `rA` by [`write_ea_back`].
|
||||
fn ea_d_update(b: &mut FunctionBuilder, ec: &EmitCtx, ra: usize, disp: i32) -> Value {
|
||||
let base = cache_read_gpr(b, ec, ra);
|
||||
let ea64 = b.ins().iadd_imm(base, disp as i64);
|
||||
b.ins().ireduce(types::I32, ea64)
|
||||
}
|
||||
|
||||
/// EA for an update-form X (indexed) load/store: `gpr[ra] + gpr[rb]` truncated
|
||||
/// to 32 bits (`ra` used directly).
|
||||
fn ea_x_update(b: &mut FunctionBuilder, ec: &EmitCtx, ra: usize, rb: usize) -> Value {
|
||||
let base = cache_read_gpr(b, ec, ra);
|
||||
let rbv = cache_read_gpr(b, ec, rb);
|
||||
let ea64 = b.ins().iadd(base, rbv);
|
||||
b.ins().ireduce(types::I32, ea64)
|
||||
}
|
||||
|
||||
/// Write an update-form EA (I32) back to `rA`, zero-extended to 64 bits — the
|
||||
/// register writeback the `u` forms perform after the access.
|
||||
fn write_ea_back(b: &mut FunctionBuilder, ec: &EmitCtx, ra: usize, ea: Value) {
|
||||
let ea64 = b.ins().uextend(types::I64, ea);
|
||||
cache_write_gpr(ec, ra, ea64);
|
||||
}
|
||||
|
||||
/// Emit a load trampoline call `fref(memenv, ea)` and return its result value.
|
||||
fn call_load(b: &mut FunctionBuilder, ec: &EmitCtx, fref: FuncRef, ea: Value) -> Value {
|
||||
let call = b.ins().call(fref, &[ec.memenv, ea]);
|
||||
@@ -1896,6 +1933,168 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
call_store(b, ec, ec.tr.write64, ea, bits);
|
||||
}
|
||||
|
||||
// ---- update-form integer loads: base load into rD, then rA = EA. ----
|
||||
PpcOpcode::lbzu => {
|
||||
let ea = ea_d_update(b, ec, instr.ra(), instr.d());
|
||||
let v = emit_inline_load(b, ec, ea, 1, ec.tr.read8);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::lbzux => {
|
||||
let ea = ea_x_update(b, ec, instr.ra(), instr.rb());
|
||||
let v = emit_inline_load(b, ec, ea, 1, ec.tr.read8);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::lhzu => {
|
||||
let ea = ea_d_update(b, ec, instr.ra(), instr.d());
|
||||
let v = emit_inline_load(b, ec, ea, 2, ec.tr.read16);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::lhzux => {
|
||||
let ea = ea_x_update(b, ec, instr.ra(), instr.rb());
|
||||
let v = emit_inline_load(b, ec, ea, 2, ec.tr.read16);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::lwzu => {
|
||||
let ea = ea_d_update(b, ec, instr.ra(), instr.d());
|
||||
let v = emit_inline_load(b, ec, ea, 4, ec.tr.read32);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::lwzux => {
|
||||
let ea = ea_x_update(b, ec, instr.ra(), instr.rb());
|
||||
let v = emit_inline_load(b, ec, ea, 4, ec.tr.read32);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::ldu => {
|
||||
let ea = ea_d_update(b, ec, instr.ra(), instr.ds());
|
||||
let v = emit_inline_load(b, ec, ea, 8, ec.tr.read64);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::ldux => {
|
||||
let ea = ea_x_update(b, ec, instr.ra(), instr.rb());
|
||||
let v = emit_inline_load(b, ec, ea, 8, ec.tr.read64);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
|
||||
// ---- update-form integer stores: base store, then rA = EA. ----
|
||||
PpcOpcode::stbu => {
|
||||
let ea = ea_d_update(b, ec, instr.ra(), instr.d());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write8, ea, v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::stbux => {
|
||||
let ea = ea_x_update(b, ec, instr.ra(), instr.rb());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write8, ea, v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::sthu => {
|
||||
let ea = ea_d_update(b, ec, instr.ra(), instr.d());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write16, ea, v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::sthux => {
|
||||
let ea = ea_x_update(b, ec, instr.ra(), instr.rb());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write16, ea, v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::stwu => {
|
||||
let ea = ea_d_update(b, ec, instr.ra(), instr.d());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write32, ea, v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::stwux => {
|
||||
let ea = ea_x_update(b, ec, instr.ra(), instr.rb());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write32, ea, v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::stdu => {
|
||||
let ea = ea_d_update(b, ec, instr.ra(), instr.ds());
|
||||
let v = cache_read_gpr(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write64, ea, v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::stdux => {
|
||||
let ea = ea_x_update(b, ec, instr.ra(), instr.rb());
|
||||
let v = cache_read_gpr(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write64, ea, v);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
|
||||
// ---- update-form FP loads: base load into fpr[rD], then rA = EA. ----
|
||||
PpcOpcode::lfsu => {
|
||||
let ea = ea_d_update(b, ec, instr.ra(), instr.d());
|
||||
let bits64 = emit_inline_load(b, ec, ea, 4, ec.tr.read32);
|
||||
let bits = b.ins().ireduce(types::I32, bits64);
|
||||
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()));
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::lfsux => {
|
||||
let ea = ea_x_update(b, ec, instr.ra(), instr.rb());
|
||||
let bits64 = emit_inline_load(b, ec, ea, 4, ec.tr.read32);
|
||||
let bits = b.ins().ireduce(types::I32, bits64);
|
||||
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()));
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::lfdu => {
|
||||
let ea = ea_d_update(b, ec, instr.ra(), instr.d());
|
||||
let bits = emit_inline_load(b, ec, ea, 8, ec.tr.read64);
|
||||
b.ins().store(MemFlags::trusted(), bits, ctxp, fpr_off(instr.rd()));
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::lfdux => {
|
||||
let ea = ea_x_update(b, ec, instr.ra(), instr.rb());
|
||||
let bits = emit_inline_load(b, ec, ea, 8, ec.tr.read64);
|
||||
b.ins().store(MemFlags::trusted(), bits, ctxp, fpr_off(instr.rd()));
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
|
||||
// ---- update-form FP stores: base store, then rA = EA. ----
|
||||
PpcOpcode::stfsu => {
|
||||
let ea = ea_d_update(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);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::stfsux => {
|
||||
let ea = ea_x_update(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);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::stfdu => {
|
||||
let ea = ea_d_update(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);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
PpcOpcode::stfdux => {
|
||||
let ea = ea_x_update(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);
|
||||
write_ea_back(b, ec, instr.ra(), ea);
|
||||
}
|
||||
|
||||
// ---- 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. ----
|
||||
@@ -1914,6 +2113,27 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
b.ins().store(MemFlags::trusted(), res, ctxp, fpr_off(instr.rd()));
|
||||
}
|
||||
|
||||
// ---- mfspr/mtspr for LR and CTR: 64-bit field copies. `covered()`
|
||||
// gates the SPR to LR/CTR, so the offset is always resolved. ----
|
||||
PpcOpcode::mfspr => {
|
||||
let field = match instr.spr() {
|
||||
crate::context::spr::LR => offset_of!(PpcContext, lr),
|
||||
crate::context::spr::CTR => offset_of!(PpcContext, ctr),
|
||||
_ => unreachable!("mfspr covered only for LR/CTR"),
|
||||
};
|
||||
let v = b.ins().load(types::I64, MemFlags::trusted(), ctxp, off(field));
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
}
|
||||
PpcOpcode::mtspr => {
|
||||
let field = match instr.spr() {
|
||||
crate::context::spr::LR => offset_of!(PpcContext, lr),
|
||||
crate::context::spr::CTR => offset_of!(PpcContext, ctr),
|
||||
_ => unreachable!("mtspr covered only for LR/CTR"),
|
||||
};
|
||||
let v = cache_read_gpr(b, ec, instr.rs());
|
||||
b.ins().store(MemFlags::trusted(), v, ctxp, off(field));
|
||||
}
|
||||
|
||||
// Unconditional branch. Target is an immediate; `pc` (= this instr's
|
||||
// address in the interpreter) is `instr.addr` at emit time.
|
||||
PpcOpcode::bx => {
|
||||
|
||||
Reference in New Issue
Block a user