From ccef16b4dadef803039e8646410de2200c388fb6 Mon Sep 17 00:00:00 2001 From: Liav Mordouch <55022020+Deftera186@users.noreply.github.com> Date: Fri, 9 Oct 2026 05:49:01 +0300 Subject: [PATCH] fix(shader): return +0 when v_div_fmas_f32 cancels with |src2| >= 2^64 With VCC set and a src2 exponent field of 128 or more (scale 2^64), v_div_fmas_f32 recomputed an fma result with exponent field 0 as an fma of the smaller multiplicand times 2^64, the other multiplicand and src2 * 2^64. For |src2| >= 2^64, src2 * 2^64 overflows to infinity, so an fma that cancels exactly returned -inf or +inf. The hardware returns +0: measured on gfx1101 for (1.0, FLT_MAX, -FLT_MAX) and (1.0, -2^100, 2^100) in all 16 combinations of IEEE mode, f32 and f16/f64 denormal mode and DX10_CLAMP. Only exact cancellations reach that path. With |src2| >= 2, a nonzero sum below 2^-126 needs a product above 1, which is a multiple of 2^-47, while src2 is a multiple of 2^-22, so a nonzero sum is at least 2^-47 and the fma result is normal. The scaled result is then the fma result times 2^64, +0 for a cancellation. The recomputation is now done only for the 2^-64 scale, when the fma result overflows. --- .../libs/prx/libSceAgcDriver/tests/execution/Division.cpp | 7 +++++++ .../recompiler/Translation/src/FloatInstructions.cpp | 8 +++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Division.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Division.cpp index 39bf75b41..09059269b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Division.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Division.cpp @@ -302,6 +302,13 @@ constexpr HelperVector HelperVectors[] = { {0x8c367fd8u, 0xbf976e24u, 0x8c367fd8u, 1u, 0xac367fd8u, 1u, 0x00000000u, 0x0c367fd8u}, {0x00f745cfu, 0x7f7fffffu, 0x00f745cfu, 0u, 0x00f745cfu, 1u, 0x40f745ceu, 0x00000000u}, {0xcb7cf32du, 0xbfe52415u, 0xbf6a1e62u, 0u, 0xcb7cf32du, 0u, 0x4be2692eu, 0x4b7cf32du}, + {0x3f800000u, 0x7f7fffffu, 0xff7fffffu, 1u, 0x1f800000u, 0u, 0x00000000u, 0xbf800000u}, + {0x3f800000u, 0xf1800000u, 0x71800000u, 1u, 0x3f800000u, 0u, 0x00000000u, 0xbf800000u}, + {0x7f7fffffu, 0xbf800000u, 0x7f7fffffu, 1u, 0x7f7fffffu, 1u, 0x00000000u, 0xff7fffffu}, + {0x3f800000u, 0xdf800000u, 0x5f800000u, 1u, 0x3f800000u, 0u, 0x00000000u, 0xbf800000u}, + {0x5f400000u, 0xdf200000u, 0x7ef00000u, 1u, 0x5f400000u, 0u, 0x00000000u, 0xdf400000u}, + {0xe40a117du, 0xbf800000u, 0xe40a117du, 1u, 0xe40a117du, 0u, 0x00000000u, 0x640a117du}, + {0x3f800000u, 0xdf7fffffu, 0x5f7fffffu, 1u, 0x3f800000u, 0u, 0x00000000u, 0xbf800000u}, }; struct DivisionVector { diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 86a354934..6543fb3ba 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -439,15 +439,13 @@ bool TranslationContext::vDivFmasF32(const RdnaInstruction& inst) { const IrU1 up(ir.Emit(IrOpcode::UGreaterThanEqual32, IrType::U1, {&exponent(*addend).Value(), &ir.Constant(128u)})); IrValue& power = ir.Emit(IrOpcode::SelectF32, IrType::F32, {&up.Value(), &ir.ConstantF32(18446744073709551616.0f), &ir.ConstantF32(5.42101086242752217e-20f)}); IrValue& plain = ir.Emit(IrOpcode::FPFma32, IrType::F32, {lhs, rhs, addend}); - const IrU32 plainExponent = exponent(plain); const IrU1 plainInfinite(ir.IEqual(ir.BitwiseAnd(ir.BitCastU32(plain), ir.Constant(0x7fffffffu)), ir.Constant(0x7f800000u))); - const IrU1 rescale(ir.LogicalOr(ir.LogicalAnd(up.Value(), ir.IEqual(plainExponent.Value(), ir.Constant(0u))), ir.LogicalAnd(ir.LogicalNot(up.Value()), plainInfinite.Value()))); + const IrU1 rescale(ir.LogicalAnd(ir.LogicalNot(up.Value()), plainInfinite.Value())); IrValue& lhsMagnitude = ir.BitwiseAnd(ir.BitCastU32(*lhs), ir.Constant(0x7fffffffu)); IrValue& rhsMagnitude = ir.BitwiseAnd(ir.BitCastU32(*rhs), ir.Constant(0x7fffffffu)); const IrU1 lhsLarger(ir.UGreaterThan(lhsMagnitude, rhsMagnitude)); - const IrU1 scaleLhs(ir.LogicalOr(ir.LogicalAnd(up.Value(), ir.LogicalNot(lhsLarger.Value())), ir.LogicalAnd(ir.LogicalNot(up.Value()), lhsLarger.Value()))); - IrValue& scaled = ir.Emit(IrOpcode::SelectF32, IrType::F32, {&scaleLhs.Value(), lhs, rhs}); - IrValue& other = ir.Emit(IrOpcode::SelectF32, IrType::F32, {&scaleLhs.Value(), rhs, lhs}); + IrValue& scaled = ir.Emit(IrOpcode::SelectF32, IrType::F32, {&lhsLarger.Value(), lhs, rhs}); + IrValue& other = ir.Emit(IrOpcode::SelectF32, IrType::F32, {&lhsLarger.Value(), rhs, lhs}); IrValue& rescaled = ir.Emit(IrOpcode::FPFma32, IrType::F32, {&ir.Emit(IrOpcode::FPMul32, IrType::F32, {&scaled, &power}), &other, &ir.Emit(IrOpcode::FPMul32, IrType::F32, {addend, &power})}); IrValue& afterwards = ir.Emit(IrOpcode::FPMul32, IrType::F32, {&plain, &power});