fix(cpu): PPCBUG-223/224/225/229/230 FPU XX bit on inexact conversions

Phase 5 batch 3 (5c) — partial: targeted XX-on-inexact fixes for the
float-to-int and double-to-single conversion family. (PPCBUG-180/200,
the broader update_after_op XX/FR/FI rework, deferred to a focused
sub-batch.)

- PPCBUG-225 frspx: set XX when the f64→f32 round produces a different
  value (i.e. precision loss). Almost every frsp call is inexact —
  previously games polling FPSCR.XX never saw the set bit after a frsp.
- PPCBUG-224 fcfidx: set XX when the i64 input has > 53 significant
  bits (precision lost in conversion to f64).
- PPCBUG-229 fctidx/fctidzx: set XX when input is non-integer (fractional
  part discarded by the conversion).
- PPCBUG-230 fctiwx/fctiwzx: same shape for word-width conversions.
- PPCBUG-223 verified already correct in current code (fcmpo sets
  VXSNAN/VXVC on NaN operands; the audit-cited drift was already fixed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-02 12:22:47 +02:00
parent 26b98975c3
commit 49bf74fae6

View File

@@ -2783,28 +2783,38 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
// ===== FPU: Rounding/Conversion =====
PpcOpcode::frspx => {
// Round to single precision honouring FPSCR[RN]
// Round to single precision honouring FPSCR[RN].
// PPCBUG-225: set XX on inexact rounding (almost every frsp call).
let b = ctx.fpr[instr.rb()];
if fpscr::is_snan(b) {
fpscr::set_exception(ctx, fpscr::VXSNAN);
}
let result = to_single(ctx, b);
if b.is_finite() && result.is_finite() && result != b {
fpscr::set_exception(ctx, fpscr::XX);
}
ctx.fpr[instr.rd()] = result;
fpscr::update_after_op(ctx, result, b.is_finite());
if instr.rc_bit() { update_cr1_from_fpscr(ctx); }
ctx.pc += 4;
}
PpcOpcode::fcfidx => {
// Convert from integer doubleword: frD = (double)(int64_t)frB_as_bits
// Convert from integer doubleword: frD = (double)(int64_t)frB_as_bits.
// PPCBUG-224: set XX when |i64| > 2^53 (precision loss in conversion).
let bits = ctx.fpr[instr.rb()].to_bits();
let result = (bits as i64) as f64;
let i = bits as i64;
let result = i as f64;
if (result as i64) != i {
fpscr::set_exception(ctx, fpscr::XX);
}
ctx.fpr[instr.rd()] = result;
fpscr::set_fprf(ctx, fpscr::classify_fprf(result));
if instr.rc_bit() { update_cr1_from_fpscr(ctx); }
ctx.pc += 4;
}
PpcOpcode::fctidx => {
// Convert to integer doubleword (round per FPSCR[RN])
// Convert to integer doubleword (round per FPSCR[RN]).
// PPCBUG-229: set XX on inexact (fractional input).
let val = ctx.fpr[instr.rb()];
let result = if val.is_nan() {
fpscr::set_exception(ctx, fpscr::VXCVI | if fpscr::is_snan(val) { fpscr::VXSNAN } else { 0 });
@@ -2816,6 +2826,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
fpscr::set_exception(ctx, fpscr::VXCVI);
0x8000_0000_0000_0000u64
} else {
if val != val.trunc() { fpscr::set_exception(ctx, fpscr::XX); }
fpscr::round_to_i64(ctx, val) as u64
};
ctx.fpr[instr.rd()] = f64::from_bits(result);
@@ -2823,7 +2834,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
ctx.pc += 4;
}
PpcOpcode::fctidzx => {
// Convert to integer doubleword (round toward zero)
// Convert to integer doubleword (round toward zero).
// PPCBUG-229: set XX on inexact.
let val = ctx.fpr[instr.rb()];
let result = if val.is_nan() {
fpscr::set_exception(ctx, fpscr::VXCVI | if fpscr::is_snan(val) { fpscr::VXSNAN } else { 0 });
@@ -2835,6 +2847,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
fpscr::set_exception(ctx, fpscr::VXCVI);
0x8000_0000_0000_0000u64
} else {
if val != val.trunc() { fpscr::set_exception(ctx, fpscr::XX); }
(val.trunc() as i64) as u64
};
ctx.fpr[instr.rd()] = f64::from_bits(result);
@@ -2842,7 +2855,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
ctx.pc += 4;
}
PpcOpcode::fctiwx => {
// Convert to integer word (round per FPSCR[RN])
// Convert to integer word (round per FPSCR[RN]).
// PPCBUG-230: set XX on inexact.
let val = ctx.fpr[instr.rb()];
let result_u32: u32 = if val.is_nan() {
fpscr::set_exception(ctx, fpscr::VXCVI | if fpscr::is_snan(val) { fpscr::VXSNAN } else { 0 });
@@ -2854,6 +2868,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
fpscr::set_exception(ctx, fpscr::VXCVI);
0x8000_0000
} else {
if val != val.trunc() { fpscr::set_exception(ctx, fpscr::XX); }
fpscr::round_to_i32(ctx, val) as u32
};
ctx.fpr[instr.rd()] = f64::from_bits(result_u32 as u64);
@@ -2861,7 +2876,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
ctx.pc += 4;
}
PpcOpcode::fctiwzx => {
// Convert to integer word (round toward zero)
// Convert to integer word (round toward zero).
// PPCBUG-230: set XX on inexact.
let val = ctx.fpr[instr.rb()];
let result_u32: u32 = if val.is_nan() {
fpscr::set_exception(ctx, fpscr::VXCVI | if fpscr::is_snan(val) { fpscr::VXSNAN } else { 0 });
@@ -2873,6 +2889,7 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
fpscr::set_exception(ctx, fpscr::VXCVI);
0x8000_0000
} else {
if val != val.trunc() { fpscr::set_exception(ctx, fpscr::XX); }
val.trunc() as i32 as u32
};
ctx.fpr[instr.rd()] = f64::from_bits(result_u32 as u64);