diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3PackLdexp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3PackLdexp.cpp index 181109a980c..e20d2a8608e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3PackLdexp.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3PackLdexp.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include @@ -102,8 +104,123 @@ constexpr std::uint32_t Expected[32][14] = { }; constexpr const char* Names[14] = {"cvt_pknorm_i16_f320", "cvt_pknorm_i16_f321", "cvt_pknorm_u16_f322", "cvt_pknorm_u16_f323", "ldexp_f324", "ldexp_f325", "ldexp_f326", "lerp_u87", "pack_b32_f168", "pack_b32_f169", "pack_b32_f1610", "pack_b32_f1611", "mul_lo_i32_0", "mul_lo_i32_1"}; -void Fill(std::uint32_t tid, std::uint32_t* words) { - std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +alignas(256) constexpr std::array PackCode{ + 0x34020084u, 0x34060086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xd711000au, 0x02020b04u, 0xd711180bu, + 0x02020b04u, 0xd711020cu, 0x22020b04u, 0xd711090du, 0x42020905u, 0xe0781000u, 0x80010a03u, 0xe0781010u, + 0x80010e03u, 0xe0781020u, 0x80011203u, 0xe0781030u, 0x80011603u, 0xbf810000u, +}; + +constexpr std::uint32_t PackRows[32][4] = { + {0xfd237c01u, 0xfc017dffu, 0x00000000u, 0x00000000u}, + {0x7e00fd23u, 0x00007c00u, 0x00000000u, 0x00000000u}, + {0x7c007dffu, 0xfd230001u, 0x00000000u, 0x00000000u}, + {0x0000fc01u, 0xfc00fd23u, 0x00000000u, 0x00000000u}, + {0x83ff7e00u, 0xc1007fffu, 0x00000000u, 0x00000000u}, + {0x7c01fe55u, 0x7fff8000u, 0x00000000u, 0x00000000u}, + {0xfc017fffu, 0x83ff7c01u, 0x00000000u, 0x00000000u}, + {0x7fff7c00u, 0x7e00fe55u, 0x00000000u, 0x00000000u}, + {0x3c00fc00u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00013c00u, 0x7dffc100u, 0x00000000u, 0x00000000u}, + {0xc1000000u, 0x3c007e00u, 0x00000000u, 0x00000000u}, + {0x7dff8000u, 0x7c013c00u, 0x00000000u, 0x00000000u}, + {0xfe550001u, 0x7c007bffu, 0x00000000u, 0x00000000u}, + {0xfc0083ffu, 0x7bfffc01u, 0x00000000u, 0x00000000u}, + {0x80007bffu, 0xfe55fc00u, 0x00000000u, 0x00000000u}, + {0x7bffc100u, 0x000183ffu, 0x00000000u, 0x00000000u}, + {0xfd237dffu, 0xfc017c01u, 0x00000000u, 0x00000000u}, + {0x7e007c00u, 0x0000fd23u, 0x00000000u, 0x00000000u}, + {0x7c000001u, 0xfd237dffu, 0x00000000u, 0x00000000u}, + {0x0000fd23u, 0xfc00fc01u, 0x00000000u, 0x00000000u}, + {0x83ff7fffu, 0xc1007e00u, 0x00000000u, 0x00000000u}, + {0x7c018000u, 0x7ffffe55u, 0x00000000u, 0x00000000u}, + {0xfc017c01u, 0x83ff7fffu, 0x00000000u, 0x00000000u}, + {0x7ffffe55u, 0x7e007c00u, 0x00000000u, 0x00000000u}, + {0x3c000000u, 0x8000fc00u, 0x00000000u, 0x00000000u}, + {0x0001c100u, 0x7dff3c00u, 0x00000000u, 0x00000000u}, + {0xc1007e00u, 0x3c000000u, 0x00000000u, 0x00000000u}, + {0x7dff3c00u, 0x7c018000u, 0x00000000u, 0x00000000u}, + {0xfe557bffu, 0x7c000001u, 0x00000000u, 0x00000000u}, + {0xfc00fc01u, 0x7bff83ffu, 0x00000000u, 0x00000000u}, + {0x8000fc00u, 0xfe557bffu, 0x00000000u, 0x00000000u}, + {0x7bff83ffu, 0x0001c100u, 0x00000000u, 0x00000000u} +}; +constexpr std::uint32_t PackExpected[32][4] = { + {0x7dff7c01u, 0xfc01fd23u, 0x7dfffc01u, 0xfc017c01u}, + {0x7c00fd23u, 0x00007e00u, 0x7c007d23u, 0x7d230000u}, + {0x00017dffu, 0xfd237c00u, 0x0001fdffu, 0xfdff7d23u}, + {0xfd23fc01u, 0xfc000000u, 0x7d237c01u, 0x7c017c00u}, + {0x7fff7e00u, 0xc10083ffu, 0x7ffffe00u, 0xfe004100u}, + {0x8000fe55u, 0x7fff7c01u, 0x00007e55u, 0x7e557fffu}, + {0x7c017fffu, 0x83fffc01u, 0x7c01ffffu, 0xffff03ffu}, + {0xfe557c00u, 0x7e007fffu, 0x7e55fc00u, 0xfc007e00u}, + {0x0000fc00u, 0x80003c00u, 0x00007c00u, 0x7c000000u}, + {0xc1003c00u, 0x7dff0001u, 0x4100bc00u, 0xbc007dffu}, + {0x7e000000u, 0x3c00c100u, 0x7e008000u, 0x80003c00u}, + {0x3c008000u, 0x7c017dffu, 0x3c000000u, 0x00007c01u}, + {0x7bff0001u, 0x7c00fe55u, 0x7bff8001u, 0x80017c00u}, + {0xfc0183ffu, 0x7bfffc00u, 0x7c0103ffu, 0x03ff7bffu}, + {0xfc007bffu, 0xfe558000u, 0x7c00fbffu, 0xfbff7e55u}, + {0x83ffc100u, 0x00017bffu, 0x03ff4100u, 0x41000001u}, + {0x7c017dffu, 0xfc01fd23u, 0x7c01fdffu, 0xfdff7c01u}, + {0xfd237c00u, 0x00007e00u, 0x7d23fc00u, 0xfc000000u}, + {0x7dff0001u, 0xfd237c00u, 0x7dff8001u, 0x80017d23u}, + {0xfc01fd23u, 0xfc000000u, 0x7c017d23u, 0x7d237c00u}, + {0x7e007fffu, 0xc10083ffu, 0x7e00ffffu, 0xffff4100u}, + {0xfe558000u, 0x7fff7c01u, 0x7e550000u, 0x00007fffu}, + {0x7fff7c01u, 0x83fffc01u, 0x7ffffc01u, 0xfc0103ffu}, + {0x7c00fe55u, 0x7e007fffu, 0x7c007e55u, 0x7e557e00u}, + {0xfc000000u, 0x80003c00u, 0x7c008000u, 0x80000000u}, + {0x3c00c100u, 0x7dff0001u, 0x3c004100u, 0x41007dffu}, + {0x00007e00u, 0x3c00c100u, 0x0000fe00u, 0xfe003c00u}, + {0x80003c00u, 0x7c017dffu, 0x0000bc00u, 0xbc007c01u}, + {0x00017bffu, 0x7c00fe55u, 0x0001fbffu, 0xfbff7c00u}, + {0x83fffc01u, 0x7bfffc00u, 0x03ff7c01u, 0x7c017bffu}, + {0x7bfffc00u, 0xfe558000u, 0x7bff7c00u, 0x7c007e55u}, + {0xc10083ffu, 0x00017bffu, 0x410003ffu, 0x03ff0001u} +}; +constexpr std::uint32_t PackExpectedIeee[32][4] = { + {0x7fff7e01u, 0xfe01ff23u, 0x7ffffe01u, 0xfe017e01u}, + {0x7c00ff23u, 0x00007e00u, 0x7c007f23u, 0x7f230000u}, + {0x00017fffu, 0xff237c00u, 0x0001ffffu, 0xffff7f23u}, + {0xff23fe01u, 0xfc000000u, 0x7f237e01u, 0x7e017c00u}, + {0x7fff7e00u, 0xc10083ffu, 0x7ffffe00u, 0xfe004100u}, + {0x8000fe55u, 0x7fff7e01u, 0x00007e55u, 0x7e557fffu}, + {0x7e017fffu, 0x83fffe01u, 0x7e01ffffu, 0xffff03ffu}, + {0xfe557c00u, 0x7e007fffu, 0x7e55fc00u, 0xfc007e00u}, + {0x0000fc00u, 0x80003c00u, 0x00007c00u, 0x7c000000u}, + {0xc1003c00u, 0x7fff0001u, 0x4100bc00u, 0xbc007fffu}, + {0x7e000000u, 0x3c00c100u, 0x7e008000u, 0x80003c00u}, + {0x3c008000u, 0x7e017fffu, 0x3c000000u, 0x00007e01u}, + {0x7bff0001u, 0x7c00fe55u, 0x7bff8001u, 0x80017c00u}, + {0xfe0183ffu, 0x7bfffc00u, 0x7e0103ffu, 0x03ff7bffu}, + {0xfc007bffu, 0xfe558000u, 0x7c00fbffu, 0xfbff7e55u}, + {0x83ffc100u, 0x00017bffu, 0x03ff4100u, 0x41000001u}, + {0x7e017fffu, 0xfe01ff23u, 0x7e01ffffu, 0xffff7e01u}, + {0xff237c00u, 0x00007e00u, 0x7f23fc00u, 0xfc000000u}, + {0x7fff0001u, 0xff237c00u, 0x7fff8001u, 0x80017f23u}, + {0xfe01ff23u, 0xfc000000u, 0x7e017f23u, 0x7f237c00u}, + {0x7e007fffu, 0xc10083ffu, 0x7e00ffffu, 0xffff4100u}, + {0xfe558000u, 0x7fff7e01u, 0x7e550000u, 0x00007fffu}, + {0x7fff7e01u, 0x83fffe01u, 0x7ffffe01u, 0xfe0103ffu}, + {0x7c00fe55u, 0x7e007fffu, 0x7c007e55u, 0x7e557e00u}, + {0xfc000000u, 0x80003c00u, 0x7c008000u, 0x80000000u}, + {0x3c00c100u, 0x7fff0001u, 0x3c004100u, 0x41007fffu}, + {0x00007e00u, 0x3c00c100u, 0x0000fe00u, 0xfe003c00u}, + {0x80003c00u, 0x7e017fffu, 0x0000bc00u, 0xbc007e01u}, + {0x00017bffu, 0x7c00fe55u, 0x0001fbffu, 0xfbff7c00u}, + {0x83fffe01u, 0x7bfffc00u, 0x03ff7e01u, 0x7e017bffu}, + {0x7bfffc00u, 0xfe558000u, 0x7bff7c00u, 0x7c007e55u}, + {0xc10083ffu, 0x00017bffu, 0x410003ffu, 0x03ff0001u} +}; +constexpr const char* PackNames[4] = { + "v_pack_b32_f16 v10, v4, v5", + "v_pack_b32_f16 v11, v4, v5 op_sel:[1,1,0]", + "v_pack_b32_f16 v12, -v4, |v5|", + "v_pack_b32_f16 v13, |v5|, -v4 op_sel:[1,0,0]" +}; + +void Fill(const std::uint32_t (&rows)[32][4]) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) std::copy(std::begin(rows[tid]), std::end(rows[tid]), &Input[tid * Inputs]); } std::array BufferDescriptor(const void* data, std::uint32_t bytes) { @@ -121,15 +238,14 @@ void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, con Require(actual == expected, std::string("vop3 pack ldexp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); } -void Run(AgcDriver::VulkanDevice& device) { - for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); +void Run(AgcDriver::VulkanDevice& device, std::span code, const std::uint32_t (&rows)[32][4], const std::optional& floatMode) { + Fill(rows); Output.fill(0xdeadbeefu); std::vector userData(8, 0u); const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); std::copy(input.begin(), input.end(), userData.begin()); std::copy(output.begin(), output.end(), userData.begin() + 4); - const std::span code(Code); const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; ShaderRecompiler::RecompileRequest request{ @@ -139,6 +255,7 @@ void Run(AgcDriver::VulkanDevice& device) { {0, 0, 0, 128} }; request.useCache = false; + request.context.floatMode = floatMode; const auto result = ShaderRecompiler::Recompile(request); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); @@ -152,14 +269,27 @@ void Check() { } } +void CheckPack(const std::uint32_t (&expected)[32][4], const char* mode) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 4; ++i) Expect(tid, out[i], expected[tid][i], (std::string(mode) + " " + PackNames[i]).c_str()); + } +} + } int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - Run(*device); + Run(*device, Code, Rows, std::nullopt); Check(); + Run(*device, PackCode, PackRows, std::nullopt); + CheckPack(PackExpected, "no float mode"); + Run(*device, PackCode, PackRows, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}); + CheckPack(PackExpected, "IEEE=0"); + Run(*device, PackCode, PackRows, ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false}); + CheckPack(PackExpectedIeee, "IEEE=1"); std::puts("vop3 pack ldexp tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp index b0a367c0dc1..a734b404ab3 100644 --- a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp @@ -188,8 +188,13 @@ void TranslationContext::vCvtPkU8F32(const RdnaInstruction& inst) { } void TranslationContext::vPackB32F16(const RdnaInstruction& inst) { - const IrU32 low = readF16Bits(sourceAt(inst, 0u)); - const IrU32 high(ir.ShiftLeftLogical(readF16Bits(sourceAt(inst, 1u)).Value(), ir.Constant(16u))); + const auto half = [&](std::uint32_t index) { + const IrU32 bits = readF16Bits(sourceAt(inst, index)); + const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffu)), ir.Constant(0x7c00u))); + return IrU32(ir.Select(nan.Value(), quietNan16(bits).Value(), bits.Value())); + }; + const IrU32 low = half(0u); + const IrU32 high(ir.ShiftLeftLogical(half(1u).Value(), ir.Constant(16u))); const IrU32 result(ir.BitwiseOr(low.Value(), high.Value())); writeOperand(inst.destination, &result.Value()); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 96682d45e44..5a1d93bd020 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -334,7 +334,7 @@ Throughout the project, every function at every stage either **does exactly what - [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted only with IEEE=1, as measured at IEEE=0 too; a positive denormal src2 counts as positive): other denormal settings are not modelled. - [v_mad_legacy_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140) rounds the product to f32 before the add, gives a +0 product for a zero factor, and flushes its f32 denormal sources, its product and its 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` 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). 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; shaders without a known float mode follow IEEE=0. `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` follows the same rule with its separately rounded product, as measured on gfx1010 (#1281): 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 arithmetic, `v_fma_mix_f32` with f16 sources, `v_cubema`, `v_mac_legacy_f32` 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_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. +- [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; 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. `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` follows the same rule with its separately rounded product, as measured on gfx1010 (#1281): 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 arithmetic, `v_fma_mix_f32` with f16 sources, `v_cubema`, `v_mac_legacy_f32` 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_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. - [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/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; 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. Tininess is detected before rounding: a product whose exact value is 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`, 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. `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` flushes its inputs, its product and its 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_mac_legacy_f32`, `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 LDS float atomics keep the previous lowering. 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.