[SPU LLVM] Use select in FMA to shorten its dependency chain (#19052)

The `FMA` instruction conditionally zeros one multiplicand if the other
is zero/denormal to emulate the xfloat's extended range. This patch
replaces that with a `select(is_non_zero, fma(a, b, c), c)` which has a
shorter dependency chain in most situations and probably allows LLVM to
better optimize it with surrounding instructions.

I added a AVX512 path that uses `vfixupimmps` to prevent a pessimization
where LLVM transforms it into a strictly serial predicate chain. I
confirmed that it works with denormals and both zeros.

https://godbolt.org/z/hsP5G43Ye
This commit is contained in:
Walter
2026-07-18 18:18:10 +10:00
committed by GitHub
parent 0fcb15ab18
commit 9b3a916af0
+18 -12
View File
@@ -8481,7 +8481,7 @@ public:
const bool a_notnan = a_known.isKnownNeverNaN() || llvm::cast<llvm::ConstantInt>(ci->getOperand(3))->getZExtValue() != 0; 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; 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 (g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::approximate)
{ {
if (a.value == b.value || (a_notnan && b_notnan)) if (a.value == b.value || (a_notnan && b_notnan))
@@ -8491,22 +8491,28 @@ public:
if (a_notnan) if (a_notnan)
{ {
const auto ma = sext<s32[4]>(fcmp_uno(a != fsplat<f32[4]>(0.))); const auto normal_fma = fma32x4(a, b, c, a_known, b_known);
const auto cb = bitcast<f32[4]>(bitcast<s32[4]>(b) & ma); return eval(select(fcmp_uno(a != fsplat<f32[4]>(0.)), normal_fma, c));
return fma32x4(a, eval(cb), c, a_known, b_known);
} }
else if (b_notnan) else if (b_notnan)
{ {
const auto mb = sext<s32[4]>(fcmp_uno(b != fsplat<f32[4]>(0.))); const auto normal_fma = fma32x4(a, b, c, a_known, b_known);
const auto ca = bitcast<f32[4]>(bitcast<s32[4]>(a) & mb); return eval(select(fcmp_uno(b != fsplat<f32[4]>(0.)), normal_fma, c));
return fma32x4(eval(ca), b, c, a_known, b_known);
} }
const auto ma = sext<s32[4]>(fcmp_uno(a != fsplat<f32[4]>(0.))); // Same number of operations well preventing a serial predicate chain pessimization
const auto mb = sext<s32[4]>(fcmp_uno(b != fsplat<f32[4]>(0.))); if (m_use_avx512)
const auto ca = bitcast<f32[4]>(bitcast<s32[4]>(a) & mb); {
const auto cb = bitcast<f32[4]>(bitcast<s32[4]>(b) & ma); // 0/denormals -> +0, else 1st operand
return fma32x4(eval(ca), eval(cb), c, a_known, b_known); const auto ca = vfixupimmps(a, b, splat<u32[4]>(0x00000800u), 0, 0xff);
const auto cb = vfixupimmps(b, a, splat<u32[4]>(0x00000800u), 0, 0xff);
return fma32x4(ca, cb, c, a_known, b_known);
}
const auto normal_fma = fma32x4(a, b, c, a_known, b_known);
const auto a_cmp = fcmp_uno(a != fsplat<f32[4]>(0.));
const auto b_cmp = fcmp_uno(b != fsplat<f32[4]>(0.));
return eval(select(a_cmp & b_cmp, normal_fma, c));
} }
else else
{ {