From 7b9997baea0d385e9e8fc2bd3abeb02110bc4148 Mon Sep 17 00:00:00 2001 From: nmzik Date: Fri, 9 Oct 2026 02:49:49 +0200 Subject: [PATCH] shader: fold exact float class masks into native NaN predicates --- .../backend/spirv/spirvEmitterAlu.cpp | 4 -- .../backend/spirv/spirvEmitterInstructions.h | 2 +- .../ir/passes/ConstantPropagation.cpp | 18 ++++++++ tests/ShaderRecompilerComputeTests.cpp | 44 +++++++++++++++++++ 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp index b3ea4c7c..c864e683 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp @@ -263,10 +263,6 @@ uint32_t EmitUMedTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32 return EmitUMax32(state, EmitUMin32(state, arg0, arg1), high_min); } -uint32_t EmitFPIsNan32(EmitterState& state, uint32_t arg0) { - return EmitNative(state, arg0, arg0); -} - uint32_t EmitFPMin32(EmitterState& state, uint32_t arg0, uint32_t arg1) { return EmitMinMaxF32Value(state, arg0, arg1, false); } diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h index c358f643..287d219b 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h @@ -157,7 +157,7 @@ inline constexpr auto EmitFPOrdLessThanEqual32 = EmitFloatCompare32; inline constexpr auto EmitFPOrdGreaterThanEqual32 = EmitFloatCompare32; inline constexpr auto EmitFPUnordGreaterThanEqual32 = EmitFloatCompare32; -uint32_t EmitFPIsNan32(EmitterState& state, uint32_t arg0); +EMIT_NATIVE(FPIsNan32, OpIsNan, U1, uint32_t) inline constexpr auto EmitFPCmpClass32 = EmitClassMaskF32; inline constexpr auto EmitFPCmpClass16 = EmitClassMaskF16; EMIT_NATIVE(FPAdd32, OpFAdd, F32, uint32_t, uint32_t) diff --git a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp index 4a6e5ec5..1be9b25d 100644 --- a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp @@ -215,6 +215,24 @@ void FoldInstruction(Block& block, Block::iterator instruction, return; case ValueOpcode::SelectU32: case ValueOpcode::SelectF32: FoldSelect(inst); return; + case ValueOpcode::FPCmpClass32: { + const auto mask = Arg(inst, 1); + if (!IsImmediate(mask, Type::U32)) { + return; + } + switch (mask.U32() & 0x3ffu) { + case 0u: Replace(inst, Value(false)); break; + case 0x3ffu: Replace(inst, Value(true)); break; + case 3u: { + const auto result = block.PrependNewInst( + instruction, ValueOpcode::FPIsNan32, {Arg(inst, 0)}); + Replace(inst, Value(&*result)); + break; + } + default: break; + } + return; + } case ValueOpcode::BitFieldInsert: { const auto base = Arg(inst, 0); const auto insert = Arg(inst, 1); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 152fcaca..f0abf9e9 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -26812,6 +26812,48 @@ TestCase VectorCompareClassF32() { O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; } +TestCase VectorCompareClassF32ImmediateAndDynamicMasks() { + using O = ShaderOpcode; + // One input for each architectural class bit, in bit order. + constexpr std::array values{ + 0x7f800001u, 0x7fc00001u, 0xff800000u, 0xbf800000u, 0x80000001u, + 0x80000000u, 0x00000000u, 0x00000001u, 0x3f800000u, 0x7f800000u}; + constexpr std::array masks{ + 0u, 0x3ffu, 3u, 7u, 0xfffffc00u, 0xffffffffu, 0xfffffc03u, 0xfffffc07u}; + TestCase test; + test.name = "VectorCompareClassF32ImmediateAndDynamicMasks"; + test.initial.assign(values.begin(), values.end()); + test.initial.insert(test.initial.end(), masks.begin(), masks.end()); + test.expected = test.initial; + auto &code = test.code; + AppendVMovU32(&code, 1, 1); + for (u32 index = 0; index < values.size(); ++index) { + AppendVMovU32(&code, 30, index * 4u); + AppendBufferLoadDword(&code, 0, 30); + for (u32 mask_index = 0; mask_index < masks.size(); ++mask_index) { + for (const bool dynamic : {false, true}) { + if (dynamic) { + AppendVMovU32(&code, 30, (values.size() + mask_index) * 4u); + AppendBufferLoadDword(&code, 2, 30); + code.push_back(EncodeVopc(0x88, Vgpr(0), 2)); + } else { + AppendVop3(&code, 0x88, 106, Vgpr(0), 255); + code.push_back(masks[mask_index]); + } + code.push_back(EncodeVop2(0x01, 3, InlineU32(0), 1)); + AppendStoreVgpr(&code, 3, static_cast(test.expected.size())); + test.expected.push_back((masks[mask_index] & (1u << index)) != 0u); + } + } + } + AppendEnd(&code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_CMP_CLASS_F32, + O::V_CNDMASK_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.required_spirv = {"OpIsNan"}; + return test; +} + TestCase VectorVopcSdwaCmpxClassF32CapturedExecMask() { using O = ShaderOpcode; @@ -36518,6 +36560,7 @@ std::vector MakeCases() { AddCase(VectorVopcCmpxNeU64CapturedExecMask); AddCase(VectorVop3CmpxNeI64CapturedExecMask); AddCase(VectorCompareClassF32); + AddCase(VectorCompareClassF32ImmediateAndDynamicMasks); AddCase(VectorVopcSdwaCmpxClassF32CapturedExecMask); cases.push_back(VectorCmpClassF16(32)); cases.push_back(VectorCmpClassF16(64)); @@ -42012,6 +42055,7 @@ int main(int argc, char **argv) { RunCase(&vulkan, VectorCompareF32DenormalModes(mode)); } RunCase(&vulkan, VectorCompareClassF32()); + RunCase(&vulkan, VectorCompareClassF32ImmediateAndDynamicMasks()); RunCase(&vulkan, VectorCompareF16Ops()); return 0; }