From 2f3f1a9c416ed41b0188dc8c0844b97862ff6c44 Mon Sep 17 00:00:00 2001 From: Alex <270190108+CouchRiv@users.noreply.github.com> Date: Sat, 10 Oct 2026 15:26:20 -0400 Subject: [PATCH] fix(shader): flush scalar f16 add/mul/sub denormals to signed zero Scalar f16 add/mul/sub ignore FLOAT_MODE denorm16 flush bits on main. Flush inputs when bit 0 is clear and results when bit 1 is clear at nearest-even. Multiply checks tininess after 11-bit rounding (0x387ff000); add/sub use 0x38800000. Co-Authored-By: Claude Opus 5.5 Co-authored-by: Cursor --- .../tests/execution/Float16Rounding.cpp | 160 +++++++++++++++++- .../Translation/src/FloatInstructions.cpp | 18 +- docs/dev/TechnicalDebt.md | 1 + 3 files changed, 171 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Float16Rounding.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Float16Rounding.cpp index ec1c19913..6bc111aa6 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Float16Rounding.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Float16Rounding.cpp @@ -292,6 +292,145 @@ constexpr Vector Vectors[] = { {0x498f4a8bu, 0x6749af47u, 0xc69e8021u, {0xabcd7c00u, 0xabcdaf47u, 0xabcd0004u}}, }; +constexpr Vector DenormalVectors[4][32] = { + { + {0u, 0x00000401u, 0x000003ffu, {0u, 0xabcd0401u, 0xabcd0000u}}, + {0u, 0x00000400u, 0x00008001u, {0u, 0xabcd0400u, 0xabcd8000u}}, + {0u, 0x00000001u, 0x00000001u, {0u, 0xabcd0000u, 0xabcd0000u}}, + {0u, 0x000003ffu, 0x00000001u, {0u, 0xabcd0000u, 0xabcd0000u}}, + {0u, 0x00008001u, 0x00008001u, {0u, 0xabcd8000u, 0xabcd0000u}}, + {0u, 0x000003ffu, 0x00008001u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00000400u, 0x00008400u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00008400u, 0x00000001u, {0u, 0xabcd8400u, 0xabcd8000u}}, + {0u, 0x00003c00u, 0x00000001u, {0u, 0xabcd3c00u, 0xabcd0000u}}, + {0u, 0x0000bc00u, 0x00000001u, {0u, 0xabcdbc00u, 0xabcd8000u}}, + {0u, 0x00003c00u, 0x00008001u, {0u, 0xabcd3c00u, 0xabcd8000u}}, + {0u, 0x00003bffu, 0x00000400u, {0u, 0xabcd3bffu, 0xabcd0000u}}, + {0u, 0x00003c01u, 0x000003ffu, {0u, 0xabcd3c01u, 0xabcd0000u}}, + {0u, 0x0000bc01u, 0x000003ffu, {0u, 0xabcdbc01u, 0xabcd8000u}}, + {0u, 0x00003bffu, 0x00008400u, {0u, 0xabcd3bffu, 0xabcd8000u}}, + {0u, 0x00003800u, 0x00000400u, {0u, 0xabcd3800u, 0xabcd0000u}}, + {0u, 0x0000b800u, 0x00000400u, {0u, 0xabcdb800u, 0xabcd8000u}}, + {0u, 0x00003800u, 0x00000401u, {0u, 0xabcd3800u, 0xabcd0000u}}, + {0u, 0x00003800u, 0x000003ffu, {0u, 0xabcd3800u, 0xabcd0000u}}, + {0u, 0x00000400u, 0x00000400u, {0u, 0xabcd0800u, 0xabcd0000u}}, + {0u, 0x00003c00u, 0x00000400u, {0u, 0xabcd3c00u, 0xabcd0400u}}, + {0u, 0x00007c00u, 0x00000001u, {0u, 0xabcd7c00u, 0xabcdfe00u}}, + {0u, 0x0000fc00u, 0x00008001u, {0u, 0xabcdfc00u, 0xabcdfe00u}}, + {0u, 0x00000001u, 0x00007c00u, {0u, 0xabcd7c00u, 0xabcdfe00u}}, + {0u, 0x00000000u, 0x00008000u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00008000u, 0x00008000u, {0u, 0xabcd8000u, 0xabcd0000u}}, + {0u, 0x00003c01u, 0x0000bc00u, {0u, 0xabcd1400u, 0xabcdbc01u}}, + {0u, 0x00007bffu, 0x00003c01u, {0u, 0xabcd7bffu, 0xabcd7c00u}}, + {0u, 0x00007e55u, 0x00000001u, {0u, 0xabcd7e55u, 0xabcd7e55u}}, + {0u, 0x00000001u, 0x0000fe01u, {0u, 0xabcdfe01u, 0xabcdfe01u}}, + {0u, 0x00007c01u, 0x000003ffu, {0u, 0xabcd7e01u, 0xabcd7e01u}}, + {0u, 0x000003ffu, 0x0000fd23u, {0u, 0xabcdff23u, 0xabcdff23u}}, + }, + { + {0u, 0x00000401u, 0x000003ffu, {0u, 0xabcd0800u, 0xabcd0000u}}, + {0u, 0x00000400u, 0x00008001u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00000001u, 0x00000001u, {0u, 0xabcd0000u, 0xabcd0000u}}, + {0u, 0x000003ffu, 0x00000001u, {0u, 0xabcd0400u, 0xabcd0000u}}, + {0u, 0x00008001u, 0x00008001u, {0u, 0xabcd8000u, 0xabcd0000u}}, + {0u, 0x000003ffu, 0x00008001u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00000400u, 0x00008400u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00008400u, 0x00000001u, {0u, 0xabcd8000u, 0xabcd8000u}}, + {0u, 0x00003c00u, 0x00000001u, {0u, 0xabcd3c00u, 0xabcd0000u}}, + {0u, 0x0000bc00u, 0x00000001u, {0u, 0xabcdbc00u, 0xabcd8000u}}, + {0u, 0x00003c00u, 0x00008001u, {0u, 0xabcd3c00u, 0xabcd8000u}}, + {0u, 0x00003bffu, 0x00000400u, {0u, 0xabcd3bffu, 0xabcd0000u}}, + {0u, 0x00003c01u, 0x000003ffu, {0u, 0xabcd3c01u, 0xabcd0400u}}, + {0u, 0x0000bc01u, 0x000003ffu, {0u, 0xabcdbc01u, 0xabcd8400u}}, + {0u, 0x00003bffu, 0x00008400u, {0u, 0xabcd3bffu, 0xabcd8000u}}, + {0u, 0x00003800u, 0x00000400u, {0u, 0xabcd3800u, 0xabcd0000u}}, + {0u, 0x0000b800u, 0x00000400u, {0u, 0xabcdb800u, 0xabcd8000u}}, + {0u, 0x00003800u, 0x00000401u, {0u, 0xabcd3800u, 0xabcd0000u}}, + {0u, 0x00003800u, 0x000003ffu, {0u, 0xabcd3800u, 0xabcd0000u}}, + {0u, 0x00000400u, 0x00000400u, {0u, 0xabcd0800u, 0xabcd0000u}}, + {0u, 0x00003c00u, 0x00000400u, {0u, 0xabcd3c00u, 0xabcd0400u}}, + {0u, 0x00007c00u, 0x00000001u, {0u, 0xabcd7c00u, 0xabcd7c00u}}, + {0u, 0x0000fc00u, 0x00008001u, {0u, 0xabcdfc00u, 0xabcd7c00u}}, + {0u, 0x00000001u, 0x00007c00u, {0u, 0xabcd7c00u, 0xabcd7c00u}}, + {0u, 0x00000000u, 0x00008000u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00008000u, 0x00008000u, {0u, 0xabcd8000u, 0xabcd0000u}}, + {0u, 0x00003c01u, 0x0000bc00u, {0u, 0xabcd1400u, 0xabcdbc01u}}, + {0u, 0x00007bffu, 0x00003c01u, {0u, 0xabcd7bffu, 0xabcd7c00u}}, + {0u, 0x00007e55u, 0x00000001u, {0u, 0xabcd7e55u, 0xabcd7e55u}}, + {0u, 0x00000001u, 0x0000fe01u, {0u, 0xabcdfe01u, 0xabcdfe01u}}, + {0u, 0x00007c01u, 0x000003ffu, {0u, 0xabcd7e01u, 0xabcd7e01u}}, + {0u, 0x000003ffu, 0x0000fd23u, {0u, 0xabcdff23u, 0xabcdff23u}}, + }, + { + {0u, 0x00000401u, 0x000003ffu, {0u, 0xabcd0401u, 0xabcd0000u}}, + {0u, 0x00000400u, 0x00008001u, {0u, 0xabcd0400u, 0xabcd8000u}}, + {0u, 0x00000001u, 0x00000001u, {0u, 0xabcd0000u, 0xabcd0000u}}, + {0u, 0x000003ffu, 0x00000001u, {0u, 0xabcd0000u, 0xabcd0000u}}, + {0u, 0x00008001u, 0x00008001u, {0u, 0xabcd8000u, 0xabcd0000u}}, + {0u, 0x000003ffu, 0x00008001u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00000400u, 0x00008400u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00008400u, 0x00000001u, {0u, 0xabcd8400u, 0xabcd8000u}}, + {0u, 0x00003c00u, 0x00000001u, {0u, 0xabcd3c00u, 0xabcd0000u}}, + {0u, 0x0000bc00u, 0x00000001u, {0u, 0xabcdbc00u, 0xabcd8000u}}, + {0u, 0x00003c00u, 0x00008001u, {0u, 0xabcd3c00u, 0xabcd8000u}}, + {0u, 0x00003bffu, 0x00000400u, {0u, 0xabcd3bffu, 0xabcd0400u}}, + {0u, 0x00003c01u, 0x000003ffu, {0u, 0xabcd3c01u, 0xabcd0000u}}, + {0u, 0x0000bc01u, 0x000003ffu, {0u, 0xabcdbc01u, 0xabcd8000u}}, + {0u, 0x00003bffu, 0x00008400u, {0u, 0xabcd3bffu, 0xabcd8400u}}, + {0u, 0x00003800u, 0x00000400u, {0u, 0xabcd3800u, 0xabcd0200u}}, + {0u, 0x0000b800u, 0x00000400u, {0u, 0xabcdb800u, 0xabcd8200u}}, + {0u, 0x00003800u, 0x00000401u, {0u, 0xabcd3800u, 0xabcd0200u}}, + {0u, 0x00003800u, 0x000003ffu, {0u, 0xabcd3800u, 0xabcd0000u}}, + {0u, 0x00000400u, 0x00000400u, {0u, 0xabcd0800u, 0xabcd0000u}}, + {0u, 0x00003c00u, 0x00000400u, {0u, 0xabcd3c00u, 0xabcd0400u}}, + {0u, 0x00007c00u, 0x00000001u, {0u, 0xabcd7c00u, 0xabcdfe00u}}, + {0u, 0x0000fc00u, 0x00008001u, {0u, 0xabcdfc00u, 0xabcdfe00u}}, + {0u, 0x00000001u, 0x00007c00u, {0u, 0xabcd7c00u, 0xabcdfe00u}}, + {0u, 0x00000000u, 0x00008000u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00008000u, 0x00008000u, {0u, 0xabcd8000u, 0xabcd0000u}}, + {0u, 0x00003c01u, 0x0000bc00u, {0u, 0xabcd1400u, 0xabcdbc01u}}, + {0u, 0x00007bffu, 0x00003c01u, {0u, 0xabcd7bffu, 0xabcd7c00u}}, + {0u, 0x00007e55u, 0x00000001u, {0u, 0xabcd7e55u, 0xabcd7e55u}}, + {0u, 0x00000001u, 0x0000fe01u, {0u, 0xabcdfe01u, 0xabcdfe01u}}, + {0u, 0x00007c01u, 0x000003ffu, {0u, 0xabcd7e01u, 0xabcd7e01u}}, + {0u, 0x000003ffu, 0x0000fd23u, {0u, 0xabcdff23u, 0xabcdff23u}}, + }, + { + {0u, 0x00000401u, 0x000003ffu, {0u, 0xabcd0800u, 0xabcd0000u}}, + {0u, 0x00000400u, 0x00008001u, {0u, 0xabcd03ffu, 0xabcd8000u}}, + {0u, 0x00000001u, 0x00000001u, {0u, 0xabcd0002u, 0xabcd0000u}}, + {0u, 0x000003ffu, 0x00000001u, {0u, 0xabcd0400u, 0xabcd0000u}}, + {0u, 0x00008001u, 0x00008001u, {0u, 0xabcd8002u, 0xabcd0000u}}, + {0u, 0x000003ffu, 0x00008001u, {0u, 0xabcd03feu, 0xabcd8000u}}, + {0u, 0x00000400u, 0x00008400u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00008400u, 0x00000001u, {0u, 0xabcd83ffu, 0xabcd8000u}}, + {0u, 0x00003c00u, 0x00000001u, {0u, 0xabcd3c00u, 0xabcd0001u}}, + {0u, 0x0000bc00u, 0x00000001u, {0u, 0xabcdbc00u, 0xabcd8001u}}, + {0u, 0x00003c00u, 0x00008001u, {0u, 0xabcd3c00u, 0xabcd8001u}}, + {0u, 0x00003bffu, 0x00000400u, {0u, 0xabcd3bffu, 0xabcd0400u}}, + {0u, 0x00003c01u, 0x000003ffu, {0u, 0xabcd3c01u, 0xabcd0400u}}, + {0u, 0x0000bc01u, 0x000003ffu, {0u, 0xabcdbc01u, 0xabcd8400u}}, + {0u, 0x00003bffu, 0x00008400u, {0u, 0xabcd3bffu, 0xabcd8400u}}, + {0u, 0x00003800u, 0x00000400u, {0u, 0xabcd3800u, 0xabcd0200u}}, + {0u, 0x0000b800u, 0x00000400u, {0u, 0xabcdb800u, 0xabcd8200u}}, + {0u, 0x00003800u, 0x00000401u, {0u, 0xabcd3800u, 0xabcd0200u}}, + {0u, 0x00003800u, 0x000003ffu, {0u, 0xabcd3800u, 0xabcd0200u}}, + {0u, 0x00000400u, 0x00000400u, {0u, 0xabcd0800u, 0xabcd0000u}}, + {0u, 0x00003c00u, 0x00000400u, {0u, 0xabcd3c00u, 0xabcd0400u}}, + {0u, 0x00007c00u, 0x00000001u, {0u, 0xabcd7c00u, 0xabcd7c00u}}, + {0u, 0x0000fc00u, 0x00008001u, {0u, 0xabcdfc00u, 0xabcd7c00u}}, + {0u, 0x00000001u, 0x00007c00u, {0u, 0xabcd7c00u, 0xabcd7c00u}}, + {0u, 0x00000000u, 0x00008000u, {0u, 0xabcd0000u, 0xabcd8000u}}, + {0u, 0x00008000u, 0x00008000u, {0u, 0xabcd8000u, 0xabcd0000u}}, + {0u, 0x00003c01u, 0x0000bc00u, {0u, 0xabcd1400u, 0xabcdbc01u}}, + {0u, 0x00007bffu, 0x00003c01u, {0u, 0xabcd7bffu, 0xabcd7c00u}}, + {0u, 0x00007e55u, 0x00000001u, {0u, 0xabcd7e55u, 0xabcd7e55u}}, + {0u, 0x00000001u, 0x0000fe01u, {0u, 0xabcdfe01u, 0xabcdfe01u}}, + {0u, 0x00007c01u, 0x000003ffu, {0u, 0xabcd7e01u, 0xabcd7e01u}}, + {0u, 0x000003ffu, 0x0000fd23u, {0u, 0xabcdff23u, 0xabcdff23u}}, + } +}; + constexpr std::array Names{"v_cvt_f16_f32", "v_add_f16", "v_mul_f16"}; std::array BufferDescriptor(const void* data, std::uint32_t count) { @@ -313,10 +452,10 @@ bool Matches(std::uint32_t actual, std::uint32_t expected) { return actual == expected || ((actual >> 16u) == (expected >> 16u) && IsNan16(actual) && IsNan16(expected)); } -void Run(AgcDriver::VulkanDevice& device, std::uint32_t first, std::uint32_t count) { +void Run(AgcDriver::VulkanDevice& device, std::uint32_t first, std::uint32_t count, std::span vectors = Vectors, std::uint32_t denorm16 = 4u, bool ieee = true, bool overflow = false) { Input.fill(0u); for (std::uint32_t lane = 0; lane < count; ++lane) { - const auto& vector = Vectors[first + lane]; + const auto& vector = vectors[first + lane]; Input[lane * Inputs] = vector.a; Input[lane * Inputs + 1] = vector.b; Input[lane * Inputs + 2] = vector.c; @@ -336,16 +475,17 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t first, std::uint32_t cou device.Target(), {0, 0, 0, 128} }; + if (denorm16 < 4u) request.context.floatMode = ShaderRecompiler::ShaderFloatMode{denorm16 << 6u, true, ieee, overflow}; request.useCache = false; const auto result = ShaderRecompiler::Recompile(request); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); } -void Check(std::uint32_t first, std::uint32_t count) { +void Check(std::uint32_t first, std::uint32_t count, std::span vectors = Vectors, std::uint32_t firstColumn = 0u) { for (std::uint32_t lane = 0; lane < count; ++lane) { - const auto& vector = Vectors[first + lane]; - for (std::uint32_t column = 0; column < Names.size(); ++column) { + const auto& vector = vectors[first + lane]; + for (std::uint32_t column = firstColumn; column < Names.size(); ++column) { const auto expected = vector.expected[column]; const auto actual = Output[lane * Results + column]; Require(Matches(actual, expected), std::string("f16 rounding: vector ") + std::to_string(first + lane) + " (" + Hex(vector.a) + ", " + Hex(vector.b) + ", " + Hex(vector.c) + ") " + Names[column] + " is " + Hex(actual) + ", expected " + Hex(expected)); @@ -365,6 +505,16 @@ int main() { Run(*device, first, count); Check(first, count); } + for (bool ieee : {false, true}) { + for (bool overflow : {false, true}) { + for (std::uint32_t denorm16 = 0u; denorm16 < 4u; ++denorm16) { + auto vectors = std::to_array(DenormalVectors[denorm16]); + if (overflow) vectors[27].expected[2] = 0xabcd7bffu; + Run(*device, 0u, 32u, vectors, denorm16, ieee, overflow); + Check(0u, 32u, vectors, 1u); + } + } + } std::puts("f16 rounding tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index b85979973..e32e9a6e2 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -211,9 +211,21 @@ bool TranslationContext::vDivFixupF16(const RdnaInstruction& inst) { } bool TranslationContext::float16Binary(const RdnaInstruction& inst, IrOpcode opcode, bool reverse) { - const IrF32 lhs = readF16AsF32(sourceAt(inst, reverse ? 1u : 0u)); - const IrF32 rhs = readF16AsF32(sourceAt(inst, reverse ? 0u : 1u)); - writeF16(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value()})), {&lhs.Value(), &rhs.Value()}); + const bool measured = (opcode == IrOpcode::FPAdd32 || opcode == IrOpcode::FPSub32 || opcode == IrOpcode::FPMul32) && floatMode.has_value() && (floatMode->floatMode & 0x0cu) == 0u; + const auto flush = [&](IrF32 value, std::uint32_t threshold) { + const IrU32 bits(ir.BitCastU32(value.Value())); + const IrU1 tiny(ir.ULessThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)), ir.Constant(threshold))); + return IrF32(ir.BitCastF32(ir.Select(tiny.Value(), ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u)), bits.Value()))); + }; + const auto read = [&](const RdnaOperand& operand) { + const IrF32 value = readF16AsF32(operand); + return measured && (floatMode->floatMode & 0x40u) == 0u ? flush(value, 0x38800000u) : value; + }; + const IrF32 lhs = read(sourceAt(inst, reverse ? 1u : 0u)); + const IrF32 rhs = read(sourceAt(inst, reverse ? 0u : 1u)); + IrF32 result(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value()})); + if (measured && (floatMode->floatMode & 0x80u) == 0u) result = flush(result, opcode == IrOpcode::FPMul32 ? 0x387ff000u : 0x38800000u); + writeF16(inst.destination, result, {&lhs.Value(), &rhs.Value()}); return true; } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 1f9822255..7af580ea2 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -384,6 +384,7 @@ Throughout the project, every function at every stage either **does exactly what - [v_mad_legacy_f32 and v_mac_legacy_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140, VOP2 0x06) round the product to f32 before the add, give a +0 product for a zero factor, and flush their f32 denormal sources, their product and their result to a zero of the same sign in every f32 denormal mode, an exception to the `FLOAT_MODE` model in [f32DenormalFlushFor](../../core/shader/recompiler/Translation/src/VectorInstructions.cpp). Measured on gfx1010 (RX 5700 XT) with `tools/hw-oracle/template.s`, assembled by the AMD Windows driver's `amd_comgr_2.dll` and launched through its `amdhip64_6.dll` instead of `oracle.c` and ROCr, over the 32 rows of the five `FmaLegacy.cpp` and the four `MacLegacy.cpp` encodings: identical in wave32 and wave64 at IEEE 0 and 1 with f32 denormal modes 0 to 3, except that the two signaling NaN rows are quieted only with IEEE=1 (#1281, #1290). A sum of two zeros takes its sign on the bits (+0 unless both are -0), as lavapipe returned -0 for +0 + -0. The PS5's gfx1013 is not measured; LLVM decodes the encoding as `v_mad_legacy_f32` there, as on gfx1010. RDNA2 differs: on gfx1031 (RX 6700 XT, measured by pandather in #1281) the encoding is `v_fma_legacy_f32` and fused, with the same zero rule and denormal-source flush, so an RDNA2 measurement is not evidence for the console. - [v_fma_mixlo_f16 and v_fma_mixhi_f16](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) round the exact product-sum once to f16 with the f64 arithmetic of v_interp_p2_f16, so they need the Float64 capability. On RDNA2 (gfx1036) they follow the `FLOAT_MODE` f16 round and denormal fields, not the f32 round field: -1.5 * 2^-24 + 2^-126 from f32 sources (0xbfc00000, 0x33800000, 0x00800000) gives 0x8001 with f16 denormals kept and round to nearest even, 0x8000 with f16 denormals flushed and 0x8002 with f16 round toward -infinity. Only f16 denormals kept with round to nearest even is translated; another f16 mode throws, as for packed f16. `FP16_OVFL` set is not measured. - [Signaling NaN inputs](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) of the f32 and f16 unary instructions (`v_sqrt/rcp/rsq/log/exp/sin/cos/fract/floor/ceil/trunc/rndne/rcp_iflag/frexp_mant`), `v_ldexp_f32/f16`, `v_frexp_mant_f16`, `v_div_fixup_f32/f16` and `v_mullit_f32` are returned quieted only with IEEE=1 and unchanged with IEEE=0, measured on RDNA2 (gfx1035) in both modes, and so are those of the f64 forms (`v_sqrt/rcp/rsq/fract/floor/ceil/trunc/rndne/frexp_mant/ldexp/div_fixup_f64`) and `v_cvt_f64_f32`, measured on RDNA2 (gfx1036) in both modes; shaders without a known float mode follow IEEE=0. `v_pack_b32_f16` quiets each signaling NaN half by the same rule, measured on RDNA2 (gfx1036) in both modes. A signaling NaN result of `v_cubesc/cubetc/cubema_f32` is quieted by the same rule and keeps the sign the instruction gives it, measured on RDNA2 (gfx1036) in both modes. `v_add/sub/subrev/mul/fma/fmac/fmamk/fmaak/mul_legacy_f32` and `v_fma_mix_f32` with f32 sources choose their NaN result on the bits, measured over 1000 operand triples in both modes: the first NaN source in operand order after its modifiers (the subtrahend negated, as the hardware adds its negation), quieted only with IEEE=1, or the default NaN 0xffc00000 for an invalid operation; in the fma forms an invalid product (inf * 0) gives the default NaN even when src2 is a NaN, and in the legacy forms a zero factor makes the product 0 even when the other factor is a NaN (a NaN src2 still wins). `v_mad_legacy_f32` and `v_mac_legacy_f32` follow the same rule with their separately rounded product, as measured on gfx1010 (#1281, #1290): a NaN source quieted only with IEEE=1, inf - inf giving 0xffc00000, the zero rule hiding a NaN factor and a NaN src2 winning. The f16 and f64 arithmetic, `v_fma_mix_f32` with f16 sources and the separately rounded mad/mac forms (not measurable on RDNA2) follow the same rule on hardware but their NaN bits are left to the driver. `v_cvt_f16_f32`, `v_cvt_f32_f64`, `v_dot2_f32_f16`, `v_fma_mixlo_f16` and `v_fma_mixhi_f16` always quiet and `v_div_scale_f32` never does: the f32 signaling NaN 0x7f812345 as src0 or src2 gives 0x7e09 from mixlo and mixhi at IEEE=0 and IEEE=1 on RDNA2 (gfx1036), where `v_fma_mix_f32` gives 0x7f812345 at IEEE=0 and 0x7fc12345 at IEEE=1. The payload of an f16 NaN source of mixlo and mixhi is left to the driver's f16 to f32 conversion, which NVIDIA replaces. +- [Scalar f16 add, sub, subrev and mul denormals](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follow the `FLOAT_MODE` f16 denormal field at nearest-even: bit 0 preserves inputs and bit 1 preserves the result; otherwise a denormal is flushed to its signed zero. Measured on RDNA2 (gfx1036) with IEEE=0/1 and FP16_OVFL=0/1, over 32 operand pairs in all four denormal modes, including signed zeros, denormals, minimum normals, infinities and NaNs. Multiply detects tininess after rounding to 11 significant bits with an unbounded exponent, before gradual half packing: `0x3bff * 0x0400` flushes, while `0x3c01 * 0x03ff` rounds to `0x0400` and is kept. Directed rounding and ternary arithmetic keep their previous denormal lowering. Shaders without a known float mode keep the previous lowering. Scalar `v_sub_f16`/`v_subrev_f16` share `float16Binary` with add/mul and were measured on gfx1036 over the same operand set as add (1156 non-NaN-input rows per mode); NaN result bits still depend on the driver as recorded above. - [Packed f16 NaN results](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (`v_pk_add/mul/fma/min/max_f16`) follow the hardware measured on RDNA2 (gfx1035) at IEEE=0 and IEEE=1 with f16 round-to-nearest-even and f16 denormals kept: with IEEE=1 a signaling NaN input is quieted and min/max return the other operand only for a quiet NaN; with IEEE=0 NaN inputs pass through unquieted and min/max ignore signaling. In `v_pk_fma_f16` an invalid product (inf * 0) gives the default NaN even when src2 is a NaN. A shader without a known float mode is translated as IEEE=0, the mode titles use; a mode with another f16 rounding or that flushes f16 denormals throws. - [f32 denormals](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follow the shader's `FLOAT_MODE` f32 denormal field (bits 5:4) when it flushes them (0, and shaders without a known float mode, as in the 0xc0 most titles use), measured on RDNA2 (gfx1035) over 1444 rows per instruction at IEEE=0 and IEEE=1. `v_add/sub/subrev/mul/mul_legacy/mullit/fma/fmac/fmamk/fmaak/min/max/min3/max3/med3/fract/floor/ceil/frexp_mant/sin/cos_f32` flush their f32 inputs and their result to a zero of the same sign (`v_mullit_f32` measured on RDNA2 (gfx1036) over 6907 rows per mode at IEEE=0 and IEEE=1, so a positive denormal src2 counts as zero); the f32 compares, `v_cvt_i32/u32/rpi_i32/flr_i32_f32`, `v_frexp_exp_i32_f32`, `v_cvt_f64_f32`, `v_cvt_f16_f32`, `v_cvt_pkrtz_f16_f32`, `v_cvt_pknorm_i16/u16_f32` and `v_cvt_pk_u8_f32` flush their f32 inputs. `v_div_fixup_f32` flushes its src1 and src2 and keeps a denormal src0 and result, measured on RDNA2 (gfx1036) over 6859 rows per mode at IEEE=0 and IEEE=1. A product is checked for tininess on its exact value: one below 2^-126 gives zero even when it rounds to 2^-126, computed exactly from the significands for `v_mul_f32`, `v_mul_legacy_f32`, `v_mullit_f32`, and for `v_fma_f32`, `v_fmac_f32`, `v_fmamk_f32`, `v_fmaak_f32` and `v_fma_mix_f32` when the addend is zero; with a nonzero addend the fused result is kept, so an exact result just below 2^-126 that rounds to it is not flushed. The hardware detects tininess after rounding to 24 bits with an unbounded exponent, measured on RDNA2 (gfx1036) for `v_mul_f32` and `v_mullit_f32` at IEEE=0 and IEEE=1: `0x3f7ffffe * 0x00800001` (2^-126 * (1 - 2^-46)) gives `0x00800000` there and zero here, while `0x3f7fffff * 0x00800000` (2^-126 * (1 - 2^-24)) gives zero on both. `v_fma_mix_f32` was measured with f32 sources only; its f16 sources are not flushed. `v_fma_mixlo_f16` and `v_fma_mixhi_f16` flush their f32 sources too and keep a product below 2^-126, measured on RDNA2 (gfx1036). `v_mad_legacy_f32` and `v_mac_legacy_f32` flush their inputs, their product and their result in every mode, see its entry. Denormal modes 1 and 2 throw for the other instructions. Not covered, and not measurable on RDNA2 (no such opcodes) or not measured: `v_mac_f32`, `v_mad_f32`, `v_madak_f32` and `v_madmk_f32` (rounded separately), `v_dot2_f32_f16`, `v_div_scale_f32` and `v_div_fmas_f32` (on hardware `v_div_fmas_f32` flushes only its result), f32 interpolation and `ds_add_f32` keep the previous lowering; `ds_min/max/cmpst_f32` flush both operands by the field, see the LDS float atomics entry. With f32 denormals kept (3) the results depend on the driver's default denormal behaviour, as no `DenormPreserve` execution mode is requested, and `v_ldexp_f32` flushes in every mode. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability.