From 9e666e4dd2aea9e5eac03cdb21a5f722d83af907 Mon Sep 17 00:00:00 2001 From: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com> Date: Tue, 6 Oct 2026 22:22:31 +0200 Subject: [PATCH] shader: support scalar byte sign extension --- .../frontend/decode/ScalarAluOps.cpp | 1 + .../frontend/decode/ShaderDecoder.cpp | 1 + .../frontend/decode/ShaderDecoder.h | 1 + .../recompiler/frontend/translate/Scalar.cpp | 8 ++- tests/ShaderRecompilerComputeTests.cpp | 70 ++++++++++++++++--- 5 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp index 193b83252..634281939 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}, + {0x19u, Opcode::S_SEXT_I32_I8}, {0x1au, Opcode::S_SEXT_I32_I16}, {0x1bu, Opcode::S_BITSET0_B32}, {0x1cu, Opcode::S_BITSET0_B64}, diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index e4f9ed68f..85ba3d495 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_I8: case Opcode::S_SEXT_I32_I16: case Opcode::S_BREV_B32: case Opcode::S_BREV_B64: diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 51dc84ff4..580719d08 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_I8, S_SEXT_I32_I16, S_ABSDIFF_I32, S_BREV_B32, diff --git a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp index 276c69a45..3653807f3 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp @@ -134,10 +134,14 @@ 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: + case O::S_SEXT_I32_I8: + case O::S_SEXT_I32_I16: { + // Sign extension leaves SCC unchanged. + const auto bits = inst.opcode == O::S_SEXT_I32_I8 ? 8u : 16u; WriteOperand(inst.dst, IR::U32(ir.Emit(IR::ValueOpcode::BitFieldSExtract, - {ReadU32(inst.src0), IR::Value(0u), IR::Value(16u)}))); + {ReadU32(inst.src0), IR::Value(0u), IR::Value(bits)}))); 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 3748322e3..495b51703 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -20590,16 +20590,23 @@ TestCase ScalarBrevB32PreservesScc() { O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; } -TestCase ScalarSextI16Captured() { +TestCase ScalarSextMasks(u32 bits, u32 wave_size) { using O = ShaderOpcode; TestCase test; - test.name = "ScalarSextI16Captured"; - constexpr u32 wave_size = 64, high = 0x80000001u; - test.initial = {0xdead0000u, 0xabcd7fffu, 0x12348000u, 0xabcfffffu}; + test.name = bits == 8 ? (wave_size == 32 ? "ScalarSextI8MasksWave32" + : "ScalarSextI8MasksWave64") + : (wave_size == 32 ? "ScalarSextI16MasksWave32" + : "ScalarSextI16MasksWave64"); + constexpr u32 high = 0x80000001u; + test.initial = bits == 8 + ? std::vector{0xdeadff00u, 0xabcdff7fu, 0x12340080u, 0xabcf00ffu} + : std::vector{0xdead0000u, 0xabcd7fffu, 0x12348000u, 0xabcfffffu}; + const u32 extended_values[] = {0u, bits == 8 ? 0x7fu : 0x7fffu, + bits == 8 ? 0xffffff80u : 0xffff8000u, 0xffffffffu}; 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]))); + const auto extended = extended_values[i]; for (const bool scc : {false, true}) { AppendVMovU32(&code, 30, i * 4u); AppendBufferLoadDword(&code, 3, 30); @@ -20607,7 +20614,8 @@ TestCase ScalarSextI16Captured() { 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. + // The I16 word is captured s_sext_i32_i16 vcc_lo, s11. + code.push_back(bits == 8 ? 0xbeea190bu : 0xbeea1a0bu); code.push_back(EncodeSMovB32(20, 106)); code.push_back(EncodeSMovB32(21, 107)); code.push_back(EncodeSMovB32(22, 253)); @@ -20628,10 +20636,12 @@ TestCase ScalarSextI16Captured() { 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::S_MOV_B64, O::S_CMP_EQ_U32, + bits == 8 ? O::S_SEXT_I32_I8 : 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.decoded_counts = {{bits == 8 ? "S_SEXT_I32_I8 vcc_lo, s11" + : "S_SEXT_I32_I16 vcc_lo, s11", 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; @@ -20641,6 +20651,38 @@ TestCase ScalarSextI16Captured() { return test; } +TestCase ScalarSextConstantsAndAliases() { + using O = ShaderOpcode; + TestCase test; + test.name = "ScalarSextConstantsAndAliases"; + auto &code = test.code; + for (const u32 opcode : {0x19u, 0x1au}) { + const u32 literal = opcode == 0x19u ? 0x12345680u : 0x12348000u; + const u32 extended = opcode == 0x19u ? 0xffffff80u : 0xffff8000u; + for (const bool scc : {false, true}) { + code.push_back(EncodeSopc(0x06, InlineU32(scc ? 1u : 0u), InlineU32(1))); + code.push_back(EncodeSop1(opcode, 10, 255)); + code.push_back(literal); + code.push_back(EncodeSop1(opcode, 10, 10)); // In-place source/destination. + code.push_back(EncodeSop1(opcode, 11, InlineU32(0))); + code.push_back(EncodeSop1(opcode, 12, InlineU32(64))); + code.push_back(EncodeSop1(opcode, 13, 193)); // Inline -1. + code.push_back(EncodeSMovB32(14, 253)); + const u32 expected[] = {extended, 0, 64, 0xffffffffu, scc ? 1u : 0u}; + for (u32 word = 0; word < 5u; ++word) { + AppendStoreSgpr(&code, 10u + word, test.expected.size()); + test.expected.push_back(expected[word]); + } + } + } + AppendEnd(&code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::S_CMP_EQ_U32, O::S_SEXT_I32_I8, O::S_SEXT_I32_I16, + O::S_MOV_B32, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.decoded_counts = {{"S_SEXT_I32_I8", 10u}, {"S_SEXT_I32_I16", 10u}}; + return test; +} + TestCase ScalarBrevB64OperandsAndMasks(u32 wave_size) { using O = ShaderOpcode; TestCase test; @@ -35779,7 +35821,11 @@ std::vector MakeCases() { AddCase(ScalarBitcmpB64DynamicOperands); AddCase(ScalarBitcmpB64IntegerConstants); AddCase(ScalarBrevB32PreservesScc); - AddCase(ScalarSextI16Captured); + for (const u32 bits : {8u, 16u}) { + cases.push_back(ScalarSextMasks(bits, 32)); + cases.push_back(ScalarSextMasks(bits, 64)); + } + AddCase(ScalarSextConstantsAndAliases); cases.push_back(ScalarBrevB64OperandsAndMasks(32)); cases.push_back(ScalarBrevB64OperandsAndMasks(64)); AddCase(ScalarBfeI32CapturedRawSignExtends); @@ -41524,7 +41570,11 @@ int main(int argc, char **argv) { } if (argc == 2 && std::strcmp(argv[1], "--scalar-sext-only") == 0) { VulkanHarness vulkan; - RunCase(&vulkan, ScalarSextI16Captured()); + for (const u32 bits : {8u, 16u}) { + RunCase(&vulkan, ScalarSextMasks(bits, 32)); + RunCase(&vulkan, ScalarSextMasks(bits, 64)); + } + RunCase(&vulkan, ScalarSextConstantsAndAliases()); return 0; } if (argc == 2 && std::strcmp(argv[1], "--flat-d16-only") == 0) {