From 5601861d673faa77c776d4f8931dcaf59c95e5e9 Mon Sep 17 00:00:00 2001 From: Claxten Date: Tue, 6 Oct 2026 04:28:20 +0400 Subject: [PATCH] shader: implement RDNA2 scalar 16-bit sign extension --- .../frontend/decode/ScalarAluOps.cpp | 1 + .../frontend/decode/ShaderDecoder.cpp | 1 + .../frontend/decode/ShaderDecoder.h | 1 + .../recompiler/frontend/translate/Scalar.cpp | 4 ++ tests/ShaderRecompilerComputeTests.cpp | 57 +++++++++++++++++++ 5 files changed, 64 insertions(+) diff --git a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp index 3a6f084c..193b8325 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp @@ -53,6 +53,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = { {0x14u, Opcode::S_FF1_I32_B64}, {0x15u, Opcode::S_FLBIT_I32_B32}, {0x16u, Opcode::S_FLBIT_I32_B64}, + {0x1au, Opcode::S_SEXT_I32_I16}, {0x1bu, Opcode::S_BITSET0_B32}, {0x1cu, Opcode::S_BITSET0_B64}, {0x1du, Opcode::S_BITSET1_B32}, diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index aa7b1e5f..0e06241c 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -503,6 +503,7 @@ std::string InstructionToString(const Instruction& inst) { OperandToString(inst.dst).c_str(), OperandToString(inst.src0).c_str())); case Opcode::S_ABS_I32: + case Opcode::S_SEXT_I32_I16: case Opcode::S_BREV_B32: case Opcode::S_BREV_B64: case Opcode::S_BCNT1_I32_B32: diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 4a2be8fd..c064b952 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -45,6 +45,7 @@ enum class Opcode { S_CMOV_B64, S_MOVK_I32, S_ABS_I32, + S_SEXT_I32_I16, S_ABSDIFF_I32, S_BREV_B32, S_BREV_B64, diff --git a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp index 4b7cec33..276c69a4 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp @@ -134,6 +134,10 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) { case O::S_ABS_I32: return SimpleInteger(inst, IR::ValueOpcode::IAbs32, IR::Type::U32, false, false, true); + case O::S_SEXT_I32_I16: + WriteOperand(inst.dst, IR::U32(ir.Emit(IR::ValueOpcode::BitFieldSExtract, + {ReadU32(inst.src0), IR::Value(0u), IR::Value(16u)}))); + return; case O::S_MUL_I32: case O::S_MULK_I32: return SimpleInteger(inst, IR::ValueOpcode::IMul32, IR::Type::U32, false, false, false); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 9d8d4de0..84efd750 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -20328,6 +20328,57 @@ TestCase ScalarBrevB32PreservesScc() { O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; } +TestCase ScalarSextI16Captured() { + using O = ShaderOpcode; + TestCase test; + test.name = "ScalarSextI16Captured"; + constexpr u32 wave_size = 64, high = 0x80000001u; + test.initial = {0xdead0000u, 0xabcd7fffu, 0x12348000u, 0xabcfffffu}; + test.expected = test.initial; + auto &code = test.code; + for (u32 i = 0; i < 4u; ++i) { + const auto extended = static_cast(static_cast(static_cast(test.initial[i]))); + for (const bool scc : {false, true}) { + AppendVMovU32(&code, 30, i * 4u); + AppendBufferLoadDword(&code, 3, 30); + code.push_back(EncodeVop1(0x02, 11, Vgpr(3))); + AppendSMovLiteral(&code, 107, high); + code.push_back(EncodeSopc(0x06, InlineU32(scc ? 1u : 0u), InlineU32(1))); + code.push_back(EncodeSop1(0x04, 126, 128)); // Scalar operations execute with empty EXEC. + code.push_back(0xbeea1a0bu); // Captured s_sext_i32_i16 vcc_lo, s11. + code.push_back(EncodeSMovB32(20, 106)); + code.push_back(EncodeSMovB32(21, 107)); + code.push_back(EncodeSMovB32(22, 253)); + code.push_back(EncodeSop1(0x04, 126, 193)); + AppendVMovU32(&code, 1, 1); + code.push_back(EncodeVop2(0x01, 2, InlineU32(0), 1)); + const u32 expected[] = {extended, high, scc ? 1u : 0u}; + for (u32 word = 0; word < 3u; ++word) { + AppendStoreSgprAtLaneDwordOffset(&code, 20u + word, 0, test.expected.size()); + test.expected.insert(test.expected.end(), wave_size, expected[word]); + } + AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, test.expected.size()); + const uint64_t mask = (uint64_t{high} << 32u) | extended; + for (u32 lane = 0; lane < wave_size; ++lane) + test.expected.push_back(static_cast((mask >> lane) & 1u)); + } + } + AppendEnd(&code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32, O::S_MOV_B32, + O::S_MOV_B64, O::S_CMP_EQ_U32, O::S_SEXT_I32_I16, + O::V_CNDMASK_B32, O::V_MOV_B32, O::V_ADD_NC_U32, + O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.decoded_counts = {{"S_SEXT_I32_I16", 8u}}; + test.required_spirv = {"OpBitFieldSExtract"}; + 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 ScalarBrevB64OperandsAndMasks(u32 wave_size) { using O = ShaderOpcode; TestCase test; @@ -35081,6 +35132,7 @@ std::vector MakeCases() { AddCase(ScalarBitcmpB64DynamicOperands); AddCase(ScalarBitcmpB64IntegerConstants); AddCase(ScalarBrevB32PreservesScc); + AddCase(ScalarSextI16Captured); cases.push_back(ScalarBrevB64OperandsAndMasks(32)); cases.push_back(ScalarBrevB64OperandsAndMasks(64)); AddCase(ScalarBfeI32CapturedRawSignExtends); @@ -40720,6 +40772,11 @@ int main(int argc, char **argv) { } return 0; } + if (argc == 2 && std::strcmp(argv[1], "--scalar-sext-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, ScalarSextI16Captured()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--flat-d16-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, FlatLoadDlcCaptured(32));