[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.
This commit is contained in:
Walter
2026-07-17 11:48:55 +10:00
committed by Elad
parent d0fdd9bb6d
commit fa418f0dbb
+44 -51
View File
@@ -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 <typename T>
static llvm_calli<f32[4], T> frest(T&& a)
{
@@ -8384,36 +8367,24 @@ public:
value_t<f32[4]> fma32x4(value_t<f32[4]> a, value_t<f32[4]> b, value_t<f32[4]> 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<f32[4]> fma32x4(value_t<f32[4]> a, value_t<f32[4]> b, value_t<f32[4]> 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<f32[4]>(ci->getOperand(1));
const auto c = value<f32[4]>(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<f32[4]>(op.ra), get_vr<f32[4]>(op.rb), get_vr<f32[4]>(op.rc)));
@@ -8488,38 +8467,45 @@ public:
const auto a = value<f32[4]>(ci->getOperand(0));
const auto b = value<f32[4]>(ci->getOperand(1));
const auto c = value<f32[4]>(ci->getOperand(2));
const bool a_notnan = llvm::cast<llvm::ConstantInt>(ci->getOperand(3))->getZExtValue() != 0;
const bool b_notnan = llvm::cast<llvm::ConstantInt>(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<llvm::ConstantInt>(ci->getOperand(3))->getZExtValue() != 0;
const bool b_notnan = b_known.isKnownNeverNaN() || llvm::cast<llvm::ConstantInt>(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<s32[4]>(fcmp_uno(a != fsplat<f32[4]>(0.)));
const auto cb = bitcast<f32[4]>(bitcast<s32[4]>(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<s32[4]>(fcmp_uno(b != fsplat<f32[4]>(0.)));
const auto ca = bitcast<f32[4]>(bitcast<s32[4]>(a) & mb);
return fma32x4(eval(ca), b, c);
return fma32x4(eval(ca), b, c, a_known, b_known);
}
const auto ma = sext<s32[4]>(fcmp_uno(a != fsplat<f32[4]>(0.)));
const auto mb = sext<s32[4]>(fcmp_uno(b != fsplat<f32[4]>(0.)));
const auto ca = bitcast<f32[4]>(bitcast<s32[4]>(a) & mb);
const auto cb = bitcast<f32[4]>(bitcast<s32[4]>(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
{