From b8946fef94a4ef45b6a2d313b097b2a50015eff7 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 4 Oct 2026 04:51:52 +0400 Subject: [PATCH] shader: translate RDNA2 signed 64-bit not-equal comparisons --- .../frontend/decode/ShaderDecoder.h | 1 + .../frontend/decode/VectorAluOps.cpp | 4 +- .../recompiler/frontend/translate/Compare.cpp | 1 + .../frontend/translate/Translate.cpp | 1 + .../recompiler/frontend/translate/Vector.cpp | 1 + tests/ShaderRecompilerComputeTests.cpp | 75 ++++++++++++++----- tests/shaderCfgTests.cpp | 11 ++- 7 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index eec07627f..c213c9219 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -446,6 +446,7 @@ enum class Opcode { V_CMP_EQ_I64, V_CMP_LT_I64, V_CMP_LE_I64, + V_CMP_NE_I64, V_CMP_LT_U64, V_CMP_EQ_U64, V_CMP_LE_U64, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 039f3982d..bc6e91b46 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -256,6 +256,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = { {0xc5u, Opcode::V_CMP_NE_U32}, {0xc6u, Opcode::V_CMP_GE_U32}, {0xc7u, Opcode::V_CMP_T_U32}, {0xa2u, Opcode::V_CMP_EQ_I64, false}, {0xa1u, Opcode::V_CMP_LT_I64, false}, {0xa3u, Opcode::V_CMP_LE_I64, false}, + {0xa5u, Opcode::V_CMP_NE_I64, false}, {0xb5u, Opcode::V_CMPX_NE_I64, false}, {0xd1u, Opcode::V_CMPX_LT_U32}, {0xd2u, Opcode::V_CMPX_EQ_U32}, {0xd3u, Opcode::V_CMPX_LE_U32}, {0xd4u, Opcode::V_CMPX_GT_U32}, {0xd5u, Opcode::V_CMPX_NE_U32}, @@ -1164,7 +1165,8 @@ VopcSdwaFields DecodeVopcSdwaFields(uint32_t modifier) { } bool SupportsVopcSdwa(Opcode opcode) { - return opcode != Opcode::UNSUPPORTED && opcode != Opcode::V_CMP_EQ_F64 && + return opcode != Opcode::UNSUPPORTED && opcode != Opcode::V_CMP_NE_I64 && + opcode != Opcode::V_CMP_EQ_F64 && opcode != Opcode::V_CMP_LE_F64 && opcode != Opcode::V_CMPX_LE_F64 && opcode != Opcode::V_CMPX_GE_F64; } diff --git a/src/graphics/shader/recompiler/frontend/translate/Compare.cpp b/src/graphics/shader/recompiler/frontend/translate/Compare.cpp index 7c68f297d..be2135a61 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Compare.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Compare.cpp @@ -31,6 +31,7 @@ void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueO const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 || opcode == IR::ValueOpcode::SLessThanEqual64 || inst.opcode == Decoder::Opcode::V_CMP_EQ_I64 || + inst.opcode == Decoder::Opcode::V_CMP_NE_I64 || inst.opcode == Decoder::Opcode::V_CMPX_NE_I64; const auto read = [&](const Decoder::Operand& operand) { // RDNA2 expands signed 64-bit integer literals by sign extension. diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index 93d45ed38..cad642821 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -906,6 +906,7 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_ case Decoder::Opcode::V_CMP_EQ_I64: case Decoder::Opcode::V_CMP_LT_I64: case Decoder::Opcode::V_CMP_LE_I64: + case Decoder::Opcode::V_CMP_NE_I64: case Decoder::Opcode::V_CMP_LT_U64: case Decoder::Opcode::V_CMP_EQ_U64: case Decoder::Opcode::V_CMP_LE_U64: diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index cd76f1b79..eb0f65b24 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -106,6 +106,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) { case O::V_CMP_GT_U64: EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, false); return; + case O::V_CMP_NE_I64: case O::V_CMP_NE_U64: EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, false); return; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index cbbc1f91d..d4e03fcf3 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -23396,8 +23396,9 @@ TestCase VectorCompareInteger64Edges() { const auto lhs = pairs[i][0], rhs = pairs[i][1]; const auto signed_lhs = std::bit_cast(lhs); const auto signed_rhs = std::bit_cast(rhs); - for (const auto [opcode, value] : std::array, 4>{{ + for (const auto [opcode, value] : std::array, 5>{{ {0xa1, signed_lhs < signed_rhs}, {0xa3, signed_lhs <= signed_rhs}, + {0xa5, lhs != rhs}, {0xe3, lhs <= rhs}, {0xe6, lhs >= rhs}}}) { test.code.push_back(EncodeVopc(opcode, Vgpr(1), 3)); store_mask(106, value); @@ -23405,8 +23406,9 @@ TestCase VectorCompareInteger64Edges() { store_mask(20, value); } // A negative literal expands by signedness, including equality comparisons. - for (const auto [opcode, value] : std::array, 4>{{ + for (const auto [opcode, value] : std::array, 5>{{ {0xa1, -1 < signed_rhs}, {0xa3, -1 <= signed_rhs}, + {0xa5, -1 != signed_rhs}, {0xa2, -1 == signed_rhs}, {0xe3, 0xffffffffull <= rhs}}}) { test.code.push_back(EncodeVopc(opcode, 255, 3)); test.code.push_back(0xffffffffu); @@ -23415,13 +23417,24 @@ TestCase VectorCompareInteger64Edges() { AppendVop3(&test.code, 0xa3, 20, Vgpr(1), 255); test.code.push_back(0xffffffffu); store_mask(20, signed_lhs <= -1); + // Captured 8395e43f382309df PC 0x20cc: s[2:3] = (s[0:1] != 0) & EXEC. + // Save the test buffer descriptor while its SGPRs hold the captured operands. + test.code.push_back(EncodeSop1(0x04, 32, 0)); + test.code.push_back(EncodeSop1(0x04, 34, 2)); + test.code.push_back(EncodeVop1(0x02, 0, Vgpr(1))); + test.code.push_back(EncodeVop1(0x02, 1, Vgpr(2))); + test.code.insert(test.code.end(), {0xd4a50002u, 0x00010000u}); + test.code.push_back(EncodeSop1(0x04, 20, 2)); + test.code.push_back(EncodeSop1(0x04, 0, 32)); + test.code.push_back(EncodeSop1(0x04, 2, 34)); + store_mask(20, lhs != 0); } AppendEnd(&test.code); test.initial.resize(test.expected.size()); test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32, - O::V_CMP_LT_I64, O::V_CMP_LE_I64, O::V_CMP_EQ_I64, + O::V_CMP_LT_I64, O::V_CMP_LE_I64, O::V_CMP_EQ_I64, O::V_CMP_NE_I64, O::V_CMP_LE_U64, O::V_CMP_GE_U64, - O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + O::S_MOV_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; test.required_spirv = {"OpSLessThan", "OpULessThanEqual", "OpUGreaterThanEqual"}; return test; } @@ -23584,25 +23597,34 @@ TestCase VectorCompareF64Edges() { return test; } -TestCase VectorCompareF64WaveMasks(u32 wave_size) { +TestCase VectorCompare64WaveMasks(u32 wave_size, bool integer_ne = false) { using O = ShaderOpcode; constexpr std::array values{0, 1, 0x8000000000000000ull, 0x7ff8000000000000ull}; constexpr u32 low_exec = 0xeeeeeeeeu, high_exec = 0x77777777u; constexpr u32 vcc_hi = 0x89abcdefu; + constexpr u32 vcc_lo = 0x12345678u; + const u32 result_reg = integer_ne ? 20 : 106; + const u32 result_bits = integer_ne ? 0xeeeeeeeeu : 0x55555555u; TestCase test; - test.name = wave_size == 32 ? "VectorCompareF64Wave32Masks" : "VectorCompareF64Wave64Masks"; - test.initial.assign(6 * wave_size, 0); + test.name = integer_ne + ? (wave_size == 32 ? "VectorCompareNeI64Wave32Masks" : "VectorCompareNeI64Wave64Masks") + : (wave_size == 32 ? "VectorCompareF64Wave32Masks" : "VectorCompareF64Wave64Masks"); + test.initial.assign((integer_ne ? 8 : 6) * wave_size, 0); for (u32 lane = 0; lane < wave_size; ++lane) { test.initial[lane] = u32(values[lane % 4]); test.initial[wave_size + lane] = u32(values[lane % 4] >> 32); } test.expected = test.initial; for (u32 lane = 0; lane < wave_size; ++lane) { - test.expected[2 * wave_size + lane] = low_exec & 0x55555555u; - test.expected[3 * wave_size + lane] = wave_size == 64 ? high_exec & 0x55555555u : vcc_hi; + test.expected[2 * wave_size + lane] = low_exec & result_bits; + test.expected[3 * wave_size + lane] = wave_size == 64 ? high_exec & result_bits : vcc_hi; test.expected[4 * wave_size + lane] = low_exec; test.expected[5 * wave_size + lane] = wave_size == 64 ? high_exec : 0; + if (integer_ne) { + test.expected[6 * wave_size + lane] = vcc_lo; + test.expected[7 * wave_size + lane] = vcc_hi; + } } auto& code = test.code; code.push_back(EncodeVop2(0x1a, 30, InlineU32(2), 0)); @@ -23611,16 +23633,24 @@ TestCase VectorCompareF64WaveMasks(u32 wave_size) { code.push_back(EncodeVop2(0x25, 31, Vgpr(31), 30)); AppendBufferLoadDword(&code, 2, 31); code.push_back(EncodeSop1(0x04, 10, 126)); - AppendSMovLiteral(&code, 107, vcc_hi); + AppendSMovLiteral(&code, result_reg + 1, vcc_hi); + if (integer_ne) { + AppendSMovLiteral(&code, 106, vcc_lo); + AppendSMovLiteral(&code, 107, vcc_hi); + } AppendSMovLiteral(&code, 126, low_exec); if (wave_size == 64) AppendSMovLiteral(&code, 127, high_exec); - AppendVop3(&code, 0x22, 106, InlineU32(0), Vgpr(1)); - code.push_back(EncodeSMovB32(20, 126)); - code.push_back(EncodeSMovB32(21, 127)); + AppendVop3(&code, integer_ne ? 0xa5 : 0x22, result_reg, InlineU32(0), Vgpr(1)); + code.push_back(EncodeSMovB32(22, 126)); + code.push_back(EncodeSMovB32(23, 127)); code.push_back(EncodeSop1(0x04, 126, 10)); for (const auto [reg, offset] : std::array, 4>{{ - {106, 2}, {107, 3}, {20, 4}, {21, 5}}}) + {result_reg, 2}, {result_reg + 1, 3}, {22, 4}, {23, 5}}}) AppendStoreSgprAtLaneDwordOffset(&code, reg, 0, offset * wave_size); + if (integer_ne) { + AppendStoreSgprAtLaneDwordOffset(&code, 106, 0, 6 * wave_size); + AppendStoreSgprAtLaneDwordOffset(&code, 107, 0, 7 * wave_size); + } AppendEnd(&code); test.compute_info.threads_num[0] = wave_size; test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1; @@ -23628,9 +23658,10 @@ TestCase VectorCompareF64WaveMasks(u32 wave_size) { test.compute_info.wave_size = wave_size; test.has_compute_info = true; test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::S_MOV_B64, O::V_LSHLREV_B32, - O::V_ADD_NC_U32, O::BUFFER_LOAD_DWORD, O::V_CMP_EQ_F64, + O::V_ADD_NC_U32, O::BUFFER_LOAD_DWORD, + integer_ne ? O::V_CMP_NE_I64 : O::V_CMP_EQ_F64, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; - test.required_spirv = {"OpFOrdEqual"}; + test.required_spirv = {integer_ne ? "OpINotEqual" : "OpFOrdEqual"}; return test; } @@ -32715,8 +32746,10 @@ std::vector MakeCases() { AddCase(VectorCompareInteger64Edges); cases.push_back(VectorCompareExecWaveMasks(32)); cases.push_back(VectorCompareExecWaveMasks(64)); - cases.push_back(VectorCompareF64WaveMasks(32)); - cases.push_back(VectorCompareF64WaveMasks(64)); + cases.push_back(VectorCompare64WaveMasks(32)); + cases.push_back(VectorCompare64WaveMasks(64)); + cases.push_back(VectorCompare64WaveMasks(32, true)); + cases.push_back(VectorCompare64WaveMasks(64, true)); AddCase(VectorF64CapturedScreenSpaceShadows); AddCase(VectorFractF64CapturedAndEdges); AddCase(VectorFractF64CapturedChainRuntimeExec); @@ -37715,6 +37748,8 @@ int main(int argc, char **argv) { if (argc == 2 && std::strcmp(argv[1], "--new-opcodes-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, VectorCompareInteger64Edges()); + RunCase(&vulkan, VectorCompare64WaveMasks(32, true)); + RunCase(&vulkan, VectorCompare64WaveMasks(64, true)); RunCase(&vulkan, VectorCompareExecWaveMasks(32)); RunCase(&vulkan, VectorCompareExecWaveMasks(64)); RunCase(&vulkan, VectorCompareF64Edges()); @@ -37827,8 +37862,8 @@ int main(int argc, char **argv) { if (argc == 2 && std::strcmp(argv[1], "--cmp-eq-f64-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, VectorCompareF64Edges()); - RunCase(&vulkan, VectorCompareF64WaveMasks(32)); - RunCase(&vulkan, VectorCompareF64WaveMasks(64)); + RunCase(&vulkan, VectorCompare64WaveMasks(32)); + RunCase(&vulkan, VectorCompare64WaveMasks(64)); RunCase(&vulkan, VectorF64CapturedScreenSpaceShadows()); RunCase(&vulkan, VectorFractF64CapturedAndEdges()); RunCase(&vulkan, VectorFractF64CapturedChainRuntimeExec()); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index c8b0bff92..35d37843a 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -4713,8 +4713,8 @@ void TestNewShaderDecoderArchitecture() { void TestNewShaderRecompilerRejectsDppOn64BitCompares() { const uint32_t opcodes[] = { - 0xa2u, 0xb5u, 0xe2u, 0xe4u, 0xe5u, - 0xf5u}; // eq_i64, cmpx_ne_i64, eq_u64, gt_u64, ne_u64, cmpx_ne_u64 + 0xa2u, 0xa5u, 0xb5u, 0xe2u, 0xe4u, 0xe5u, + 0xf5u}; // eq_i64, ne_i64, cmpx_ne_i64, eq_u64, gt_u64, ne_u64, cmpx_ne_u64 for (const auto opcode : opcodes) { const uint32_t shader[] = { EncodeVopc(opcode, 250u, 0u), // DPP escape in SRC0 @@ -4732,6 +4732,13 @@ void TestNewShaderRecompilerRejectsDppOn64BitCompares() { Check((compare.unsupported_reason.find("VOPC DPP modifier is not supported for opcode") != std::string::npos), "64-bit VOPC DPP rejection reason was not explicit"); } + const uint32_t sdwa[] = {EncodeVopc(0xa5u, 249u, 0u), 0x06060000u}; + ShaderRecompiler::Decoder::Instruction compare; + ShaderRecompiler::Decoder::DecodeInstruction(sdwa, 0u, compare); + Check(compare.opcode == ShaderRecompiler::Decoder::Opcode::UNSUPPORTED && + compare.word_count == 2u && + compare.unsupported_reason.find("VOPC SDWA modifier is not supported") != std::string::npos, + "V_CMP_NE_I64 accepted an illegal SDWA encoding"); } void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() {