diff --git a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp index f9641ede8..b7a4994ba 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp @@ -74,7 +74,8 @@ constexpr OpcodeMap SOPC_OPCODE_LIST[] = { {0x03u, Opcode::S_CMP_GE_I32}, {0x04u, Opcode::S_CMP_LT_I32}, {0x05u, Opcode::S_CMP_LE_I32}, {0x06u, Opcode::S_CMP_EQ_U32}, {0x07u, Opcode::S_CMP_LG_U32}, {0x08u, Opcode::S_CMP_GT_U32}, {0x09u, Opcode::S_CMP_GE_U32}, {0x0au, Opcode::S_CMP_LT_U32}, {0x0bu, Opcode::S_CMP_LE_U32}, - {0x0cu, Opcode::S_BITCMP0_B32}, {0x0du, Opcode::S_BITCMP1_B32}, {0x12u, Opcode::S_CMP_EQ_U64}, + {0x0cu, Opcode::S_BITCMP0_B32}, {0x0du, Opcode::S_BITCMP1_B32}, + {0x0eu, Opcode::S_BITCMP0_B64}, {0x0fu, Opcode::S_BITCMP1_B64}, {0x12u, Opcode::S_CMP_EQ_U64}, {0x13u, Opcode::S_CMP_LG_U64}, }; diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 2ec7a9edf..ad9a400fb 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -73,6 +73,8 @@ enum class Opcode { S_SUB_I32, S_BITCMP0_B32, S_BITCMP1_B32, + S_BITCMP0_B64, + S_BITCMP1_B64, S_BITSET0_B32, S_BITSET1_B32, S_BITSET0_B64, diff --git a/src/graphics/shader/recompiler/frontend/translate/Integer.cpp b/src/graphics/shader/recompiler/frontend/translate/Integer.cpp index e16a3861e..4e518b803 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Integer.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Integer.cpp @@ -507,6 +507,18 @@ bool Translator::S_BITCMP_B32(const Decoder::Instruction& inst, bool expected) { return true; } +bool Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) { + const auto value = ReadU32Pair(inst.src0); + const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(63u))); + const auto word_bit = ir.BitwiseAnd(offset, IR::U32(IR::Value(31u))); + const auto word = + ir.Select(ir.ULessThan(offset, IR::U32(IR::Value(32u))), value[0], value[1]); + const auto bit = + IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {word, word_bit, IR::Value(1u)})); + WriteCompareResult(inst.dst, ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u)))); + return true; +} + bool Translator::V_ALIGNBIT_B32(const Decoder::Instruction& inst) { const auto hi = ReadU32(inst.src0); const auto lo = ReadU32(inst.src1); diff --git a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp index 850734b72..6a5024c70 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp @@ -208,6 +208,8 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) { case O::S_BFE_U64: return S_BFE_U64(inst); case O::S_BITCMP0_B32: return S_BITCMP_B32(inst, false); case O::S_BITCMP1_B32: return S_BITCMP_B32(inst, true); + case O::S_BITCMP0_B64: return S_BITCMP_B64(inst, false); + case O::S_BITCMP1_B64: return S_BITCMP_B64(inst, true); case O::S_PACK_LL_B32_B16: return PackB16(inst, false, false); case O::S_PACK_LH_B32_B16: return PackB16(inst, false, true); case O::S_PACK_HH_B32_B16: return PackB16(inst, true, true); diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index bdf6cecea..a1d1d5953 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -205,6 +205,7 @@ private: bool V_BFE_U32(const Decoder::Instruction& inst, bool sign); bool V_BFI_B32(const Decoder::Instruction& inst); bool S_BITCMP_B32(const Decoder::Instruction& inst, bool expected); + bool S_BITCMP_B64(const Decoder::Instruction& inst, bool expected); bool V_ALIGNBIT_B32(const Decoder::Instruction& inst); bool V_ALIGNBYTE_B32(const Decoder::Instruction& inst); bool V_LSHL_ADD_U32(const Decoder::Instruction& inst); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 8bfab5282..593486ea6 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -17057,6 +17057,117 @@ TestCase ScalarGetpcWritesNextInstructionPc() { {O::S_GETPC_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; } +TestCase ScalarBitcmpB64DynamicOperands() { + using O = ShaderOpcode; + + struct Input { + u32 low; + u32 high; + u32 index; + }; + const Input inputs[] = { + {1u, 0u, 0u}, {0x80000000u, 0u, 31u}, {0u, 1u, 32u}, + {0u, 0x80000000u, 63u}, {1u, 0u, 64u}, + {0x80000000u, 0u, 95u}, {0u, 1u, 96u}, + {0u, 0x80000000u, 127u}, {1u, 0u, 128u}, + {0u, 0x80000000u, 0xffffffffu}, {0u, 0u, 17u}, + {0xffffffffu, 0xffffffffu, 42u}, + {0x89abcdefu, 0x76543210u, 4u}, + {0x89abcdefu, 0x76543210u, 36u}, + }; + TestCase test; + test.name = "ScalarBitcmpB64DynamicOperands"; + for (const auto &input : inputs) { + test.initial.insert(test.initial.end(), {input.low, input.high, input.index}); + } + test.expected = test.initial; + for (u32 i = 0; i < std::size(inputs); ++i) { + // Load the value and index on the GPU so the compares cannot constant-fold. + for (u32 component = 0; component < 3; ++component) { + AppendVMovU32(&test.code, 30, (i * 3 + component) * 4); + AppendBufferLoadDword(&test.code, 0, 30); + test.code.push_back(EncodeVop1(0x02, 20 + component, Vgpr(0))); + } + const uint64_t value = uint64_t{inputs[i].low} | (uint64_t{inputs[i].high} << 32); + const u32 bit = static_cast((value >> (inputs[i].index & 63u)) & 1u); + for (u32 expected_bit = 0; expected_bit < 2; ++expected_bit) { + const bool scc = bit == expected_bit; + test.code.push_back(EncodeSopc(0x0e + expected_bit, 20, 22)); + test.code.push_back(EncodeSop2(0x0a, 24, InlineU32(1), InlineU32(0))); + AppendStoreSgpr(&test.code, 24, static_cast(test.expected.size())); + test.expected.push_back(scc ? 1u : 0u); + + // Exercise SCC-driven control flow as well as S_CSELECT_B32. + test.code.push_back(EncodeSopp(0x05, 2)); // s_cbranch_scc1 to the true arm. + test.code.push_back(EncodeSMovB32(24, InlineU32(3))); + test.code.push_back(EncodeSopp(0x02, 1)); // s_branch past the true arm. + test.code.push_back(EncodeSMovB32(24, InlineU32(7))); + AppendStoreSgpr(&test.code, 24, static_cast(test.expected.size())); + test.expected.push_back(scc ? 7u : 3u); + } + } + AppendEnd(&test.code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32, + O::S_BITCMP0_B64, O::S_BITCMP1_B64, O::S_CSELECT_B32, + O::S_CBRANCH_SCC1, O::S_BRANCH, O::S_MOV_B32, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.required_spirv = {"OpBitFieldUExtract", "OpULessThan", "OpSelect"}; + return test; +} + +TestCase ScalarBitcmpB64IntegerConstants() { + using O = ShaderOpcode; + + struct Source { + u32 encoding; + uint64_t bits; + }; + // Negative integer inlines sign-extend; B64 literals zero-extend. + const Source sources[] = { + {128u, 0u}, {129u, 1u}, {192u, 64u}, + {193u, 0xffffffffffffffffull}, {208u, 0xfffffffffffffff0ull}, + {255u, 0xffffffffu}, {255u, 0x80000000u}, + }; + const u32 indices[] = {0u, 4u, 6u, 31u, 32u, 63u, 64u, 127u}; + TestCase test; + test.name = "ScalarBitcmpB64IntegerConstants"; + test.initial.assign(std::begin(indices), std::end(indices)); + test.expected = test.initial; + for (u32 i = 0; i < std::size(indices); ++i) { + AppendVMovU32(&test.code, 30, i * 4); + AppendBufferLoadDword(&test.code, 0, 30); + test.code.push_back(EncodeVop1(0x02, 22, Vgpr(0))); + for (const auto &source : sources) { + const u32 bit = static_cast((source.bits >> (indices[i] & 63u)) & 1u); + for (u32 expected_bit = 0; expected_bit < 2; ++expected_bit) { + test.code.push_back(EncodeSopc(0x0e + expected_bit, source.encoding, 22)); + if (source.encoding == 255u) { + test.code.push_back(static_cast(source.bits)); + } + test.code.push_back(EncodeSop2(0x0a, 24, InlineU32(1), InlineU32(0))); + AppendStoreSgpr(&test.code, 24, static_cast(test.expected.size())); + test.expected.push_back(bit == expected_bit ? 1u : 0u); + } + } + } + // An instruction has one literal word even when both sources select it. + for (u32 expected_bit = 0; expected_bit < 2; ++expected_bit) { + test.code.push_back(EncodeSopc(0x0e + expected_bit, 255u, 255u)); + test.code.push_back(0x8000001fu); // Select bit 31 of the same zero-extended literal. + test.code.push_back(EncodeSop2(0x0a, 24, InlineU32(1), InlineU32(0))); + AppendStoreSgpr(&test.code, 24, static_cast(test.expected.size())); + test.expected.push_back(expected_bit); + } + AppendEnd(&test.code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32, + O::S_BITCMP0_B64, O::S_BITCMP1_B64, O::S_CSELECT_B32, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.required_spirv = {"OpBitFieldUExtract"}; + return test; +} + TestCase ScalarBitfieldPack() { using O = ShaderOpcode; @@ -27172,6 +27283,8 @@ std::vector MakeCases() { cases.push_back(ScalarSubvectorLoops(64)); AddCase(ScalarGetpcWritesNextInstructionPc); AddCase(ScalarBitfieldPack); + AddCase(ScalarBitcmpB64DynamicOperands); + AddCase(ScalarBitcmpB64IntegerConstants); AddCase(ScalarBrevB32PreservesScc); AddCase(ScalarBfeI32CapturedRawSignExtends); AddCase(BitfieldExtractWidthPastEndEdges);