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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user