From 32ea0869e59eaedf08a9c94ae8cad973fa4a4a36 Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 9 Sep 2026 13:11:48 +0400 Subject: [PATCH] shader: implement S_ORN2_SAVEEXEC_B32 --- .../frontend/decode/ScalarAluOps.cpp | 1 + .../frontend/decode/ShaderDecoder.cpp | 1 + .../frontend/decode/ShaderDecoder.h | 1 + .../recompiler/frontend/translate/Control.cpp | 33 +++++---- .../recompiler/frontend/translate/Scalar.cpp | 3 + tests/ShaderRecompilerComputeTests.cpp | 69 +++++++++++++++++++ tests/shaderCfgTests.cpp | 10 +++ 7 files changed, 105 insertions(+), 13 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp index 197c637d1..815787037 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp @@ -62,6 +62,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = { {0x37u, Opcode::S_ANDN1_SAVEEXEC_B64}, {0x3bu, Opcode::S_BITREPLICATE_B64_B32}, {0x3cu, Opcode::S_AND_SAVEEXEC_B32}, + {0x40u, Opcode::S_ORN2_SAVEEXEC_B32}, {0x44u, Opcode::S_ANDN1_SAVEEXEC_B32}, }; diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index 436f79949..49e4aef5c 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -485,6 +485,7 @@ std::string InstructionToString(const Instruction& inst) { case Opcode::S_WQM_B64: case Opcode::S_QUADMASK_B64: case Opcode::S_AND_SAVEEXEC_B32: + case Opcode::S_ORN2_SAVEEXEC_B32: case Opcode::S_ANDN1_SAVEEXEC_B32: case Opcode::S_AND_SAVEEXEC_B64: case Opcode::S_ORN2_SAVEEXEC_B64: diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 60e9632d1..90816f81b 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -54,6 +54,7 @@ enum class Opcode { S_GETPC_B64, S_SETPC_B64, S_AND_SAVEEXEC_B32, + S_ORN2_SAVEEXEC_B32, S_ANDN1_SAVEEXEC_B32, S_AND_SAVEEXEC_B64, S_ORN2_SAVEEXEC_B64, diff --git a/src/graphics/shader/recompiler/frontend/translate/Control.cpp b/src/graphics/shader/recompiler/frontend/translate/Control.cpp index eebfec2e0..366e1da69 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Control.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Control.cpp @@ -24,27 +24,34 @@ Decoder::Operand ConditionOperand(Decoder::OperandKind kind) { void Translator::S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation, bool negate_exec, bool negate_source, bool write_64) { + if (!write_64) { + // Read the encoded scalar word and preserve EXEC_HI, including in wave32. + const auto old = ir.GetExecLo(); + const auto src = ReadU32(inst.src0); + const auto lhs = negate_exec ? ir.BitwiseNot(old) : old; + const auto rhs = negate_source ? ir.BitwiseNot(src) : src; + IR::U32 result; + switch (operation) { + case IR::ValueOpcode::LogicalAnd: result = ir.BitwiseAnd(lhs, rhs); break; + case IR::ValueOpcode::LogicalOr: result = ir.BitwiseOr(lhs, rhs); break; + default: EXIT("unsupported SAVEEXEC operation"); + } + WriteRawU32(inst.dst, old); + WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecLo), result); + ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u)))); + return; + } const auto old = ir.GetExec(); const auto src = ReadMask(inst.src0); const auto lhs = negate_exec ? ir.LogicalNot(old) : old; const auto rhs = negate_source ? ir.LogicalNot(src) : src; - auto result = IR::U1(ir.Emit(operation, {lhs, rhs})); - if (write_64) { - WriteMask(inst.dst, old, true); - } else { - WriteRawU32(inst.dst, ir.GetExecLo()); - if (program.wave_size == 64u) { - const auto low_half = - ir.ULessThan(IR::U32(ir.Emit(IR::ValueOpcode::LaneId)), IR::U32(IR::Value(32u))); - result = IR::U1(ir.Emit(IR::ValueOpcode::SelectU1, {low_half, result, old})); - } - } + const auto result = IR::U1(ir.Emit(operation, {lhs, rhs})); + WriteMask(inst.dst, old, true); const auto mask = BallotMask(result); ir.SetExec(result); ir.SetExecLo(mask[0]); ir.SetExecHi(mask[1]); - ir.SetScc( - ir.INotEqual(write_64 ? ir.BitwiseOr(mask[0], mask[1]) : mask[0], IR::U32(IR::Value(0u)))); + ir.SetScc(ir.INotEqual(ir.BitwiseOr(mask[0], mask[1]), IR::U32(IR::Value(0u)))); } void Translator::ADD_U32(const Decoder::Instruction& inst, bool vector, bool use_carry_in) { diff --git a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp index d119422ba..30dacff02 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp @@ -23,6 +23,9 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) { case O::S_ANDN1_SAVEEXEC_B32: S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, true, false); return true; + case O::S_ORN2_SAVEEXEC_B32: + S_SAVEEXEC(inst, IR::ValueOpcode::LogicalOr, true, false, false); + return true; case O::S_AND_SAVEEXEC_B64: S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, false, true); return true; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index b6e8258e3..7bd090f85 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -14968,6 +14968,69 @@ TestCase ScalarOrn2SaveexecUsesSourceOrNotExec() { O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; } +TestCase ScalarOrn2SaveexecB32(u32 wave_size, u32 threads) { + using O = ShaderOpcode; + constexpr u32 exec_hi = 0xa5a55a5au; + constexpr u32 vcc_hi = 0x13579bdfu; + struct Case { + u32 exec_lo, source, destination, result; + }; + constexpr Case cases[] = { + {0x8000000cu, 126, 106, 0xffffffffu}, // Captured vcc_lo, exec_lo. + {0x0000000fu, 6, 6, 0xfffffff3u}, // Aliased SGPR source/destination. + {0xffffffffu, 128, 8, 0}, // SCC ignores the nonzero high half. + {0xffffffffu, 107, 8, vcc_hi}, + {0xffffffffu, 127, 8, exec_hi}, + {0xffffffffu, 253, 8, 1}, // SCC is a scalar value, not a mask. + }; + TestCase test; + test.name = wave_size == 64 ? "ScalarOrn2SaveexecB32Wave64" + : threads == 32 ? "ScalarOrn2SaveexecB32Wave32" + : "ScalarOrn2SaveexecB32PartialWave"; + auto &code = test.code; + for (const auto &item : cases) { + AppendVMovU32(&code, 2, 0); + AppendSMovLiteral(&code, 126, item.exec_lo); + AppendSMovLiteral(&code, 127, exec_hi); + AppendSMovLiteral(&code, 107, vcc_hi); + AppendSMovLiteral(&code, 6, 3); + code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(0))); + code.push_back(item.source == 126 ? 0xbeea407eu + : EncodeSop1(0x40, item.destination, item.source)); + const u32 snapshots[] = {item.destination, 126, 127, 107, 253}; + for (u32 i = 0; i < std::size(snapshots); ++i) { + code.push_back(EncodeSMovB32(20 + i, snapshots[i])); + } + AppendVMovU32(&code, 2, 1); + code.push_back(EncodeSop1(0x04, 126, 193u)); // Restore EXEC for readback. + const u32 expected[] = {item.exec_lo, item.result, exec_hi, vcc_hi, + item.result != 0 ? 1u : 0u}; + for (u32 i = 0; i < std::size(expected); ++i) { + AppendStoreSgprAtLaneDwordOffset(&code, 20 + i, 0, + static_cast(test.expected.size())); + test.expected.insert(test.expected.end(), threads, expected[i]); + } + AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, + static_cast(test.expected.size())); + for (u32 lane = 0; lane < threads; ++lane) { + const u32 mask = lane < 32 ? item.result : exec_hi; + test.expected.push_back((mask >> (lane % 32)) & 1u); + } + } + AppendEnd(&code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::S_MOV_B32, O::S_MOV_B64, O::S_CMP_EQ_U32, + O::S_ORN2_SAVEEXEC_B32, O::V_MOV_B32, O::V_LSHLREV_B32, + O::V_ADD_NC_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.decoded_counts = {{"S_ORN2_SAVEEXEC_B32 vcc_lo, exec_lo", 1}}; + test.compute_info.threads_num[0] = threads; + 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 ScalarGetpcWritesNextInstructionPc() { using O = ShaderOpcode; @@ -23993,6 +24056,9 @@ std::vector MakeCases() { AddCase(ScalarFlbitI32B32Gpu); AddCase(ScalarSaveExecOps); AddCase(ScalarOrn2SaveexecUsesSourceOrNotExec); + cases.push_back(ScalarOrn2SaveexecB32(32, 32)); + cases.push_back(ScalarOrn2SaveexecB32(64, 64)); + cases.push_back(ScalarOrn2SaveexecB32(32, 4)); AddCase(ScalarGetpcWritesNextInstructionPc); AddCase(ScalarBitfieldPack); AddCase(ScalarBrevB32PreservesScc); @@ -28448,6 +28514,9 @@ int main(int argc, char **argv) { RunCase(&vulkan, DispatcherIrreducibleControlFlow()); RunCase(&vulkan, ScalarSaveExecOps()); RunCase(&vulkan, ScalarOrn2SaveexecUsesSourceOrNotExec()); + RunCase(&vulkan, ScalarOrn2SaveexecB32(32, 32)); + RunCase(&vulkan, ScalarOrn2SaveexecB32(64, 64)); + RunCase(&vulkan, ScalarOrn2SaveexecB32(32, 4)); RunCase(&vulkan, ScalarNotB64UpdatesScc()); RunCase(&vulkan, ScalarSelectB64PreservesMaskProvenance()); RunCase(&vulkan, ScalarConditionalMoveB64()); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index efba9b081..e3205a232 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -4033,6 +4033,16 @@ void TestNewShaderDecoderArchitecture() { Check(GetInstructionFamily(EncodeDs0(0x36)) == Family::DS, "decoder did not classify DS directly"); + const uint32_t orn2_saveexec_code[] = {0xbeea407eu}; + Instruction orn2_saveexec; + ShaderRecompiler::Decoder::DecodeInstruction(orn2_saveexec_code, 0u, orn2_saveexec); + Check(orn2_saveexec.family == Family::SOP1 && + orn2_saveexec.opcode == Opcode::S_ORN2_SAVEEXEC_B32 && + orn2_saveexec.word_count == 1u && orn2_saveexec.src_count == 1u && + orn2_saveexec.dst.kind == OperandKind::VccLo && + orn2_saveexec.src0.kind == OperandKind::ExecLo, + "decoder rejected or misdecoded captured S_ORN2_SAVEEXEC_B32"); + const uint32_t offset_code[] = {0u, EncodeVop1(0x01, 2, 3)}; Instruction direct; ShaderRecompiler::Decoder::DecodeInstruction(offset_code, 1u, direct);