From c3546577b6ceb50d8f22cbbac20c15ab24e6c7e9 Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 9 Sep 2026 19:17:45 +0400 Subject: [PATCH] shader: implement S_WQM_B32 Co-authored-by: Brandon Strong --- .../frontend/decode/ScalarAluOps.cpp | 1 + .../frontend/decode/ShaderDecoder.cpp | 1 + .../frontend/decode/ShaderDecoder.h | 1 + .../recompiler/frontend/translate/Control.cpp | 17 +++- .../recompiler/frontend/translate/Scalar.cpp | 3 +- .../frontend/translate/Translator.h | 2 +- tests/ShaderRecompilerComputeTests.cpp | 82 +++++++++++++++++++ 7 files changed, 101 insertions(+), 6 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp index f86d1b583..eb0d82d3b 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp @@ -41,6 +41,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = { {0x06u, Opcode::S_CMOV_B64}, {0x07u, Opcode::S_NOT_B32}, {0x08u, Opcode::S_NOT_B64}, + {0x09u, Opcode::S_WQM_B32}, {0x0au, Opcode::S_WQM_B64}, {0x0bu, Opcode::S_BREV_B32}, {0x0fu, Opcode::S_BCNT1_I32_B32}, diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index ab5850317..8bf1bc311 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -487,6 +487,7 @@ std::string InstructionToString(const Instruction& inst) { case Opcode::S_BITSET0_B64: case Opcode::S_BITSET1_B64: case Opcode::S_NOT_B64: + case Opcode::S_WQM_B32: case Opcode::S_WQM_B64: case Opcode::S_QUADMASK_B64: case Opcode::S_AND_SAVEEXEC_B32: diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index c0a06473d..b5c9c41d3 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -63,6 +63,7 @@ enum class Opcode { S_ANDN1_SAVEEXEC_B64, S_NOT_B32, S_NOT_B64, + S_WQM_B32, S_WQM_B64, S_ADD_U32, S_ADDC_U32, diff --git a/src/graphics/shader/recompiler/frontend/translate/Control.cpp b/src/graphics/shader/recompiler/frontend/translate/Control.cpp index f8a73abef..12e87029c 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Control.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Control.cpp @@ -301,10 +301,19 @@ void Translator::S_MOV_B64(const Decoder::Instruction& inst) { } } -void Translator::S_WQM_B64(const Decoder::Instruction& inst) { - const auto mask_valid = ReadMaskValid(inst.src0); - const auto result = - IR::U64(ir.Emit(IR::ValueOpcode::WqmU64, {ReadOperand(inst.src0, IR::Type::U64)})); +void Translator::S_WQM(const Decoder::Instruction& inst, bool wide) { + const auto source = wide ? ReadU64(inst.src0) + : ir.ConstructU64(ReadU32(inst.src0), IR::U32(IR::Value(0u))); + const auto result = IR::U64(ir.Emit(IR::ValueOpcode::WqmU64, {source})); + if (!wide) { + const auto low = ir.CompositeExtract(result, 0); + // A 32-bit write preserves the other EXEC/VCC half and invalidates any + // overlapping SGPR-pair mask provenance through the ordinary scalar path. + WriteRawU32(inst.dst, low); + ir.SetScc(ir.INotEqual(low, IR::U32(IR::Value(0u)))); + return; + } + const auto mask_valid = ReadMaskValid(inst.src0); WriteOperand(DestinationOperand(inst), result); if (inst.dst.kind == Decoder::OperandKind::Sgpr) { const auto dst = static_cast(inst.dst.reg); diff --git a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp index e30f79541..0eb5bc95d 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp @@ -8,7 +8,8 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) { case O::S_MOV_B32: case O::S_MOVK_I32: MOV_B32(inst, false); return true; case O::S_MOV_B64: S_MOV_B64(inst); return true; - case O::S_WQM_B64: S_WQM_B64(inst); return true; + case O::S_WQM_B32: S_WQM(inst, false); return true; + case O::S_WQM_B64: S_WQM(inst, true); return true; case O::S_GETPC_B64: S_GETPC_B64(inst); return true; case O::S_SETPC_B64: return true; case O::S_SUBVECTOR_LOOP_BEGIN: S_SUBVECTOR_LOOP(inst, true); return true; diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index 0372dde7b..1ee1cace4 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -234,7 +234,7 @@ private: void ScalarSelect64(const Decoder::Instruction& inst, const Decoder::Operand& false_source); void MOV_B32(const Decoder::Instruction& inst, bool apply_float_modifiers); void S_MOV_B64(const Decoder::Instruction& inst); - void S_WQM_B64(const Decoder::Instruction& inst); + void S_WQM(const Decoder::Instruction& inst, bool wide); void V_MOVRELS_B32(const Decoder::Instruction& inst); void V_MOVRELD_B32(const Decoder::Instruction& inst); void V_READFIRSTLANE_B32(const Decoder::Instruction& inst); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 3c5bfb879..d3983a479 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -15882,6 +15882,84 @@ TestCase ScalarSelectB64PreservesMaskProvenance() { O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; } +TestCase ScalarWqmB32Masks(u32 wave_size) { + using O = ShaderOpcode; + TestCase test; + test.name = wave_size == 64 ? "ScalarWqmB32MasksWave64" : "ScalarWqmB32MasksWave32"; + auto &code = test.code; + const auto store_scalar = [&](u32 reg, u32 expected) { + AppendStoreSgprAtLaneDwordOffset(&code, reg, 0, + static_cast(test.expected.size())); + test.expected.insert(test.expected.end(), wave_size, expected); + }; + const auto store_marker = [&](u32 lo, u32 hi) { + AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, + static_cast(test.expected.size())); + for (u32 lane = 0; lane < wave_size; ++lane) { + const u32 mask = lane < 32 ? lo : hi; + test.expected.push_back(((mask >> (lane % 32)) & 1u) != 0 ? 9u : 0u); + } + }; + constexpr u32 other_half = 0x2468ace0u; + constexpr u32 masks[] = {0, 1, 8, 0x10, 0x100, 0x80000000u, 0x12481248u, ~0u}; + for (u32 mask : masks) { + u32 expanded = 0; + for (u32 bit = 0; bit < 32; bit += 4) { + if ((mask & (0xfu << bit)) != 0) expanded |= 0xfu << bit; + } + AppendVMovU32(&code, 2, 0); + AppendSMovLiteral(&code, 126, mask); + AppendSMovLiteral(&code, 127, other_half); + code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(mask == 0 ? 0 : 1))); + code.push_back(0xbefe097eu); // Captured S_WQM_B32 EXEC_LO, EXEC_LO. + code.push_back(EncodeSMovB32(20, 126)); + code.push_back(EncodeSMovB32(21, 127)); + code.push_back(EncodeSMovB32(22, 253)); + AppendVMovU32(&code, 2, 9); + code.push_back(EncodeSop1(0x04, 126, 193)); + store_scalar(20, expanded); + store_scalar(21, other_half); + store_scalar(22, mask != 0 ? 1u : 0u); + store_marker(expanded, other_half); + } + // VALU-produced masks keep WQM dynamic and carry provenance through S_MOV_B64. + // Updating either word must invalidate an old SGPR-pair predicate and preserve + // the other word. Wave32 still reads/writes the scalar high half independently. + for (u32 base : {8u, 106u, 126u}) { + for (u32 half : {0u, 1u}) { + AppendVMovU32(&code, 2, 0); + code.push_back(EncodeVop2(0x1b, 1, InlineU32(31), 0)); + code.push_back(EncodeVopc(0xc2, InlineU32(3), 1)); // Lanes 3 and 35. + code.push_back(EncodeSop1(0x04, base, 106)); + code.push_back(EncodeSop1(0x09, base + half, base + half)); + code.push_back(EncodeSMovB32(20, base)); + code.push_back(EncodeSMovB32(21, base + 1)); + code.push_back(EncodeSMovB32(22, 253)); + code.push_back(EncodeSop1(0x04, 126, base)); + AppendVMovU32(&code, 2, 9); + code.push_back(EncodeSop1(0x04, 126, 193)); + const u32 lo = half == 0 ? 0xfu : 8u; + const u32 hi = wave_size == 32 ? 0u : half == 1 ? 0xfu : 8u; + store_scalar(20, lo); + store_scalar(21, hi); + store_scalar(22, wave_size == 32 && half == 1 ? 0u : 1u); + store_marker(lo, hi); + } + } + AppendEnd(&code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::S_WQM_B32, O::S_MOV_B32, O::S_MOV_B64, O::S_CMP_EQ_U32, + O::V_CMP_EQ_U32, O::V_MOV_B32, O::V_AND_B32, O::V_ADD_NC_U32, + O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.decoded_counts = {{"S_WQM_B32 exec_lo, exec_lo", 9}}; + test.compute_info.threads_num[0] = wave_size; + test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1; + test.compute_info.thread_ids_num = 1; + test.compute_info.wave_size = wave_size; + test.has_compute_info = true; + return test; +} + TestCase ScalarWqmB64SelectsSccDomain() { using O = ShaderOpcode; @@ -24538,6 +24616,8 @@ std::vector MakeCases() { AddCase(ScalarSelectB64PreservesMaskProvenance); AddCase(ScalarWqmB64SelectsSccDomain); AddCase(ScalarWqmB64PreservesPartialMasks); + cases.push_back(ScalarWqmB32Masks(32)); + cases.push_back(ScalarWqmB32Masks(64)); AddCase(ScalarMaskProvenanceOverlapAndMixedBinary); AddCase(ScalarLiteral); AddCase(VectorMoves); @@ -29034,6 +29114,8 @@ int main(int argc, char **argv) { RunCase(&vulkan, ScalarConditionalMoveB64PreservesMasks()); RunCase(&vulkan, ScalarWqmB64SelectsSccDomain()); RunCase(&vulkan, ScalarWqmB64PreservesPartialMasks()); + RunCase(&vulkan, ScalarWqmB32Masks(32)); + RunCase(&vulkan, ScalarWqmB32Masks(64)); RunCase(&vulkan, ScalarMaskProvenanceOverlapAndMixedBinary()); RunCase(&vulkan, ScratchIsPrivatePerInvocation()); RunCase(&vulkan, Vop1MoveRelDestination());