[iterate-4A] jit: FP-move tier — lfs/stfs/lfd/stfd + fmr/fabs/fneg (diff-clean, 39.82% native)
Cover the fpscr-free FP moves. Validated bit-exact via the in-process
differential harness on a full boot+movie run (movie plays, clean exit):
checked 146.2M blocks, 39.82% native (up from 38.78%), MISMATCHES=0
Coverage (all fpscr-free — pure data movement, no rounding flags):
* FP loads: lfs/lfsx (load single, IEEE-widen to the f64 FPR via
read32 -> bitcast F32 -> fpromote F64), lfd/lfdx (pure 64-bit bit copy).
* FP stores: stfs/stfsx (fdemote f64 -> f32 -> bitcast i32 -> write32),
stfd/stfdx (pure 64-bit bit copy). Stores reuse the reservation-kicking
write trampolines.
* FP reg moves (Rc=0 only — the `.` forms update CR1 from fpscr): fmr (bit
copy), fabs (band_imm i64::MAX, clear sign), fneg (bxor_imm i64::MIN, flip
sign) — bit ops that exactly match Rust f64 copy/abs/neg.
Note: the +1% lift is small because the movie's FP-heavy blocks almost always
mix an FP load with FP *arithmetic* (fadds/fmuls/fmadds), which stays
uncovered — it updates fpscr (FPRF/FI/FR, invalid-op flags), not yet lowered.
The moves that landed are the pure data-copy blocks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user