From fa418f0dbb76ce60a9bf4eef741405564836f010 Mon Sep 17 00:00:00 2001 From: Walter Date: Fri, 17 Jul 2026 11:48:55 +1000 Subject: [PATCH] [SPU LLVM] Add `KnownFPClass` to FMAs Replaced the denormal/zero check in `fma32x4` with LLVM's `KnownFPClass` analysis. That method now includes a overload which expects the caller to provide the known struct. This is used for memorization with the FMA instructions, where the range clamping and instruction specific special value handling are combined. `FMS` and `FNMS` has a larger search depth as it can potentially shorten the chain by a lot. --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 95 +++++++++++++--------------- 1 file changed, 44 insertions(+), 51 deletions(-) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 9d562b7a23..ddfe600a7a 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -7816,23 +7816,6 @@ public: return clamp_smax(v); } - // Checks for postive and negative zero, or Denormal (treated as zero) - // If sign is +-1 check equality againts all sign bits - bool is_spu_float_zero(v128 a, int sign = 0) - { - for (u32 i = 0; i < 4; i++) - { - const u32 exponent = a._u32[i] & 0x7f800000u; - - if (exponent || (sign && (sign >= 0) != (a._s32[i] >= 0))) - { - // Normalized number - return false; - } - } - return true; - } - template static llvm_calli frest(T&& a) { @@ -8384,36 +8367,24 @@ public: value_t fma32x4(value_t a, value_t b, value_t c) { + const auto a_known = get_known_fp_class<2>(a, spu_zero_fp_classes); + const auto b_known = get_known_fp_class<2>(b, spu_zero_fp_classes); + + return fma32x4(a, b, c, a_known, b_known); + } + + value_t fma32x4(value_t a, value_t b, value_t c, llvm::KnownFPClass a_known, llvm::KnownFPClass b_known) + { + const auto c_known = get_known_fp_class<2>(c, spu_zero_fp_classes); + // Optimization: Emit only a floating multiply if the addend is zero // This is odd since SPU code could just use the FM instruction, but it seems common enough - if (auto [ok, data] = get_const_vector(c.value, m_pos); ok) + if (c_known.isKnownAlways(spu_zero_fp_classes)) { - if (is_spu_float_zero(data, 0)) - { - return eval(a * b); - } + return eval(a * b); } - if ([&]() - { - if (auto [ok, data] = get_const_vector(a.value, m_pos); ok) - { - if (is_spu_float_zero(data, 0)) - { - return true; - } - } - - if (auto [ok, data] = get_const_vector(b.value, m_pos); ok) - { - if (is_spu_float_zero(data, 0)) - { - return true; - } - } - - return false; - }()) + if (a_known.isKnownAlways(spu_zero_fp_classes) || b_known.isKnownAlways(spu_zero_fp_classes)) { // Just return the added value if either a or b are +-0 return c; @@ -8454,7 +8425,15 @@ public: const auto b = value(ci->getOperand(1)); const auto c = value(ci->getOperand(2)); - return fma32x4(eval(-clamp_smax(a)), clamp_smax(b), c); + constexpr auto interested_classes = llvm::FPClassTest::fcNan | llvm::FPClassTest::fcInf | spu_zero_fp_classes; + auto a_known = get_known_fp_class<4>(a, interested_classes); + auto b_known = get_known_fp_class<4>(b, interested_classes); + + const auto a_clamp = clamp_smax(a, a_known); + const auto b_clamp = clamp_smax(b, b_known); + + a_known.fneg(); + return fma32x4(eval(-a_clamp), b_clamp, c, a_known, b_known); }); set_vr(op.rt4, fnms(get_vr(op.ra), get_vr(op.rb), get_vr(op.rc))); @@ -8488,38 +8467,45 @@ public: const auto a = value(ci->getOperand(0)); const auto b = value(ci->getOperand(1)); const auto c = value(ci->getOperand(2)); - const bool a_notnan = llvm::cast(ci->getOperand(3))->getZExtValue() != 0; - const bool b_notnan = llvm::cast(ci->getOperand(4))->getZExtValue() != 0; + + constexpr auto interested_classes = llvm::FPClassTest::fcNan | spu_zero_fp_classes; + + // The "mask away with zero multiplier" case is already optimized for in fma32x4 + const auto a_known = get_known_fp_class<2>(a, interested_classes); + const auto b_known = get_known_fp_class<2>(b, interested_classes); + + const bool a_notnan = a_known.isKnownNeverNaN() || llvm::cast(ci->getOperand(3))->getZExtValue() != 0; + const bool b_notnan = b_known.isKnownNeverNaN() || llvm::cast(ci->getOperand(4))->getZExtValue() != 0; if (g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::approximate) { if (a.value == b.value || (a_notnan && b_notnan)) { - return fma32x4(a, b, c); + return fma32x4(a, b, c, a_known, b_known); } if (a_notnan) { const auto ma = sext(fcmp_uno(a != fsplat(0.))); const auto cb = bitcast(bitcast(b) & ma); - return fma32x4(a, eval(cb), c); + return fma32x4(a, eval(cb), c, a_known, b_known); } else if (b_notnan) { const auto mb = sext(fcmp_uno(b != fsplat(0.))); const auto ca = bitcast(bitcast(a) & mb); - return fma32x4(eval(ca), b, c); + return fma32x4(eval(ca), b, c, a_known, b_known); } const auto ma = sext(fcmp_uno(a != fsplat(0.))); const auto mb = sext(fcmp_uno(b != fsplat(0.))); const auto ca = bitcast(bitcast(a) & mb); const auto cb = bitcast(bitcast(b) & ma); - return fma32x4(eval(ca), eval(cb), c); + return fma32x4(eval(ca), eval(cb), c, a_known, b_known); } else { - return fma32x4(a, b, c); + return fma32x4(a, b, c, a_known, b_known); } }); @@ -8799,7 +8785,14 @@ public: } #endif - return fma32x4(clamp_smax(a), clamp_smax(b), eval(-c)); + constexpr auto interested_classes = llvm::FPClassTest::fcNan | llvm::FPClassTest::fcInf | spu_zero_fp_classes; + const auto a_known = get_known_fp_class<4>(a, interested_classes); + const auto b_known = get_known_fp_class<4>(b, interested_classes); + + const auto a_clamp = clamp_smax(a, a_known); + const auto b_clamp = clamp_smax(b, b_known); + + return fma32x4(a_clamp, b_clamp, eval(-c), a_known, b_known); } else {