fix(cpu): PPCBUG-181/182/183/202/203/205 FMA VXISI + NaN sign preservation
Phase 5 batch 2 (5b): VXISI / NaN handling for the FMA family. The 8 FMA opcodes (fmaddx/fmaddsx/fmsubx/fmsubsx/fnmaddx/fnmaddsx/fnmsubx/ fnmsubsx) all share two fix shapes: 1. VXISI on the add/sub step. The previous code passed `a*c` to check_invalid_add, which has separate rounding from the FMA. In extreme cases this gives the wrong sign (PPCBUG-202) or wrong infinity status. Worse, fmsub/fnmadd/fnmsub had NO add-step VXISI check at all (PPCBUG-181/182/203). The fnmsub pattern is the canonical Newton- Raphson step — the most common FPU path in Xbox 360 graphics code. 2. NaN sign preservation in fnmadd/fnmsub. ISA Book I §4.3.4 forbids negation of a NaN FMA result; Rust's unary `-` flips the IEEE-754 sign bit (PPCBUG-183/205). Fixes: - fpscr.rs: new helper `check_invalid_fma_add(ctx, a, c, b, sub)` that derives VXISI from input properties (mathematical-product sign + b sign) instead of from the lossy `a*c` value. Also covers SNaN. - interpreter.rs: all 8 FMA arms now use the new helper; fnmadd[s]/ fnmsub[s] gate the negation on `!fma.is_nan()`. Tests: - fmsub_inf_minus_inf_sets_vxisi: regression for PPCBUG-203. - fnmadd_nan_input_preserves_nan_sign: regression for PPCBUG-205. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -152,6 +152,33 @@ pub fn check_invalid_add(ctx: &mut PpcContext, a: f64, b: f64, sub: bool) -> boo
|
||||
false
|
||||
}
|
||||
|
||||
/// FMA-aware add/sub VXISI check. Per PPCBUG-202+203: the previous code
|
||||
/// passed `a*c` as `lhs` to `check_invalid_add`, which suffers from two
|
||||
/// rounding errors and can spuriously raise/miss VXISI in extreme cases.
|
||||
/// This helper derives the mathematical product's sign and infinity status
|
||||
/// from the inputs directly.
|
||||
///
|
||||
/// `sub` follows the same semantics as `check_invalid_add`:
|
||||
/// - false (add): VXISI when product and b have opposite signs at infinity
|
||||
/// - true (sub): VXISI when product and b have same sign at infinity
|
||||
pub fn check_invalid_fma_add(ctx: &mut PpcContext, a: f64, c: f64, b: f64, sub: bool) -> bool {
|
||||
let mut bits = 0u32;
|
||||
if is_snan(a) || is_snan(c) || is_snan(b) { bits |= VXSNAN; }
|
||||
let product_is_inf = (a.is_infinite() || c.is_infinite())
|
||||
&& a != 0.0 && c != 0.0
|
||||
&& !a.is_nan() && !c.is_nan();
|
||||
if product_is_inf && b.is_infinite() {
|
||||
let p_neg = a.is_sign_negative() != c.is_sign_negative();
|
||||
let b_neg = b.is_sign_negative();
|
||||
let same_sign = p_neg == b_neg;
|
||||
if (sub && same_sign) || (!sub && !same_sign) {
|
||||
bits |= VXISI;
|
||||
}
|
||||
}
|
||||
if bits != 0 { set_exception(ctx, bits); return true; }
|
||||
false
|
||||
}
|
||||
|
||||
pub fn check_invalid_mul(ctx: &mut PpcContext, a: f64, b: f64) -> bool {
|
||||
let mut bits = 0u32;
|
||||
if is_snan(a) || is_snan(b) { bits |= VXSNAN; }
|
||||
|
||||
Reference in New Issue
Block a user