From 5a705dd15f9312f7db29baa91dfd93a2c895cb53 Mon Sep 17 00:00:00 2001 From: Matthew Hurley Date: Fri, 25 Sep 2026 08:27:44 +1000 Subject: [PATCH] shader: support V_CMPX_EQ_U16 (#816) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * shader: support V_CMPX_EQ_U16 Decode gfx11 VOPC 0xba and lower it as an unsigned 16-bit equality compare with CMPX EXEC-mask semantics. 🤖 Generated with Codebuff 🤖 Co-Authored-By: Codebuff * shader: correct CMPX EQ U16 runtime expectations Remove the added CFG tests and correct two EXEC expectations in the compute regression case. --------- Co-authored-by: Matthew Hurley <171001439+theantipopau@users.noreply.github.com> Co-authored-by: Codebuff Co-authored-by: nmzik --- .../frontend/decode/ShaderDecoder.h | 1 + .../frontend/decode/VectorAluOps.cpp | 3 +- .../recompiler/frontend/translate/Vector.cpp | 3 + tests/ShaderRecompilerComputeTests.cpp | 74 +++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 2f57eaf4..a37f2d87 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -404,6 +404,7 @@ enum class Opcode { V_CMP_LE_U16, V_CMP_GT_U16, V_CMPX_LT_U16, + V_CMPX_EQ_U16, V_CMPX_GT_U16, V_CMP_NE_U16, V_CMP_GE_U16, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 5890a634..ce098fdc 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -231,7 +231,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = { {0xa9u, Opcode::V_CMP_LT_U16}, {0xaau, Opcode::V_CMP_EQ_U16}, {0xabu, Opcode::V_CMP_LE_U16}, {0xacu, Opcode::V_CMP_GT_U16}, {0xadu, Opcode::V_CMP_NE_U16}, {0xaeu, Opcode::V_CMP_GE_U16}, - {0xb9u, Opcode::V_CMPX_LT_U16, false}, + {0xb9u, Opcode::V_CMPX_LT_U16, false}, {0xbau, Opcode::V_CMPX_EQ_U16, false}, {0xbcu, Opcode::V_CMPX_GT_U16}, {0xc0u, Opcode::V_CMP_F_U32}, {0xc1u, Opcode::V_CMP_LT_U32}, {0xc2u, Opcode::V_CMP_EQ_U32}, {0xc3u, Opcode::V_CMP_LE_U32}, {0xc4u, Opcode::V_CMP_GT_U32}, @@ -1454,6 +1454,7 @@ bool IsVopcCompareExec(Opcode opcode) { case Opcode::V_CMPX_NE_I64: case Opcode::V_CMPX_NE_U64: case Opcode::V_CMPX_LT_U16: + case Opcode::V_CMPX_EQ_U16: case Opcode::V_CMPX_GT_U16: case Opcode::V_CMPX_LT_F16: case Opcode::V_CMPX_EQ_F16: diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index 57f18a77..90b438d6 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -141,6 +141,9 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) { case O::V_CMPX_LT_U16: EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, true); return true; + case O::V_CMPX_EQ_U16: + EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, true); + return true; case O::V_CMP_LE_U16: EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false, false); return true; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 90f399ea..07c695b7 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -22492,6 +22492,74 @@ TestCase VectorVopcCmpxLtU16CapturedSdwaExecMask() { return test; } +TestCase VectorVopcCmpxEqU16SdwaCompactVop3ExecMask() { + using O = ShaderOpcode; + struct CompareCase { + u32 lhs; + u32 rhs; + u32 incoming_exec; + u32 expected_exec; + u32 encoding = 0; // 0: SDWA, 1: compact, 2: VOP3, 3: SDWA sign-extend. + }; + const std::array cases{{ + {0x12340000u, 0x56780000u, 1, 1}, // Equal low half; ignore high half. + {0xffff0001u, 0xabcd0001u, 1, 1}, + {0x00000000u, 0x12340000u, 1, 1}, + {0xaaaa5555u, 0xbbbb5555u, 1, 1}, + {0x00000000u, 0x00000001u, 1, 0}, + {0x0000ffffu, 0x00000000u, 1, 0}, + {0x7fff8000u, 0x00018000u, 1, 1}, // Equal low halves despite different high halves. + {0x00000000u, 0x00010000u, 1, 1}, + {0x12340001u, 0x12340002u, 1, 0, 1}, + {0x00008000u, 0x0000ffffu, 1, 0, 2}, + {0x0001ffffu, 0x12340001u, 1, 0, 3}, // Sign-extended low halves differ. + {0x00000001u, 0x00010001u, 0, 0}, // CMPX cannot reactivate an inactive lane. + }}; + constexpr u32 vcc_hi = 0x89abcdefu; + TestCase test; + test.name = "VectorVopcCmpxEqU16SdwaCompactVop3ExecMask"; + for (const auto &entry : cases) { + test.initial.push_back(entry.lhs); + } + test.expected = test.initial; + auto &code = test.code; + for (u32 i = 0; i < cases.size(); ++i) { + const auto &entry = cases[i]; + AppendVMovU32(&code, 30, i * 4u); + AppendBufferLoadDword(&code, 0, 30); // Runtime input prevents constant folding. + AppendVMovU32(&code, 1, entry.rhs); + AppendSMovLiteral(&code, 106, entry.rhs); + AppendSMovLiteral(&code, 107, vcc_hi); + code.push_back(EncodeSMovB32(126, InlineU32(entry.incoming_exec))); + switch (entry.encoding) { + case 1: code.push_back(0x7d740300u); break; // compact v0, v1 + case 2: code.insert(code.end(), {0xd4ba007eu, 0x00020300u}); break; + case 3: + code.push_back(EncodeVopc(0xba, 249u, 1u)); + code.push_back(EncodeVopcSdwa(0u, 0u, 0u, 6u, 6u, 1u, 1u)); + break; + default: + code.push_back(EncodeVopc(0xba, 249u, 1u)); + code.push_back(EncodeVopcSdwa(0u)); + break; + } + code.push_back(EncodeSMovB32(20, 126)); // Snapshot EXEC before restoring it. + code.push_back(EncodeSMovB32(21, 127)); + code.push_back(EncodeSMovB32(126, InlineU32(1u))); + const u32 out = static_cast(cases.size()) + i * 4u; + AppendStoreSgprPair(&code, 20, out); + AppendStoreSgprPair(&code, 106, out + 2u); // CMPX must not overwrite either VCC half. + test.expected.insert(test.expected.end(), + {entry.expected_exec, 0u, entry.rhs, vcc_hi}); + } + AppendEnd(&code); + test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD, + O::V_CMPX_EQ_U16, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.decoded_counts = {{"V_CMPX_EQ_U16", cases.size()}}; + test.required_spirv = {"OpIEqual"}; + return test; +} + TestCase VectorVopcCmpNgtF16CapturedSdwaAndEdges() { using O = ShaderOpcode; enum Encoding { Captured, Compact, Vop3NegAbs, HighWord, ScalarDst, SdwaNegAbs }; @@ -28588,6 +28656,7 @@ std::vector MakeCases() { AddCase(VectorVopcSdwaCmpxWritesExecMask); AddCase(VectorVopcCmpxGtU16CapturedSdwaExecMask); AddCase(VectorVopcCmpxLtU16CapturedSdwaExecMask); + AddCase(VectorVopcCmpxEqU16SdwaCompactVop3ExecMask); AddCase(VectorVopcCmpNgtF16CapturedSdwaAndEdges); AddCase(VectorVopcCmpNltF16CapturedSdwaAndEdges); AddCase(VectorVopcCmpxNgtF16CapturedSdwaExecMask); @@ -33386,6 +33455,11 @@ int main(int argc, char **argv) { CheckEmbeddedFetchVertexOffset(); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--cmpx-eq-u16-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, VectorVopcCmpxEqU16SdwaCompactVop3ExecMask()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--cmpx-lt-u16-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, VectorVopcCmpxLtU16CapturedSdwaExecMask());