From d247d3d60c6a2f3c38552b53bb1a4e9d1bc15311 Mon Sep 17 00:00:00 2001 From: TheCruZ Date: Sat, 10 Oct 2026 03:35:20 +0200 Subject: [PATCH] shader: complete signed 64-bit integer comparisons --- .../frontend/decode/ShaderDecoder.h | 7 +- .../frontend/decode/VectorAluOps.cpp | 44 ++++--- .../recompiler/frontend/translate/Compare.cpp | 12 +- .../frontend/translate/Translate.cpp | 14 ++- .../frontend/translate/Translator.h | 2 +- .../recompiler/frontend/translate/Vector.cpp | 17 +++ tests/ShaderRecompilerComputeTests.cpp | 78 +++++++++--- tests/shaderCfgTests.cpp | 114 +++++++++++++----- 8 files changed, 218 insertions(+), 70 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 251b5be5b..3ef35ff90 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -487,11 +487,14 @@ enum class Opcode { V_CMP_NE_U32, V_CMP_GE_U32, V_CMP_T_U32, + // Keep V_CMP_F_I64 to V_CMPX_T_U64 contiguous: VectorAluOps.cpp tests this range. V_CMP_F_I64, - V_CMP_EQ_I64, V_CMP_LT_I64, + V_CMP_EQ_I64, V_CMP_LE_I64, + V_CMP_GT_I64, V_CMP_NE_I64, + V_CMP_GE_I64, V_CMP_T_I64, V_CMP_F_U64, V_CMP_LT_U64, @@ -505,7 +508,9 @@ enum class Opcode { V_CMPX_LT_I64, V_CMPX_EQ_I64, V_CMPX_LE_I64, + V_CMPX_GT_I64, V_CMPX_NE_I64, + V_CMPX_GE_I64, V_CMPX_T_I64, V_CMPX_F_U64, V_CMPX_LT_U64, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 094b8dce2..5cb4a8de1 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -278,25 +278,26 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = { {0xc1u, Opcode::V_CMP_LT_U32}, {0xc2u, Opcode::V_CMP_EQ_U32}, {0xc3u, Opcode::V_CMP_LE_U32}, {0xc4u, Opcode::V_CMP_GT_U32}, {0xc5u, Opcode::V_CMP_NE_U32}, {0xc6u, Opcode::V_CMP_GE_U32}, - {0xc7u, Opcode::V_CMP_T_U32}, {0xa2u, Opcode::V_CMP_EQ_I64, false}, - {0xa1u, Opcode::V_CMP_LT_I64, false}, {0xa3u, Opcode::V_CMP_LE_I64, false}, - {0xa5u, Opcode::V_CMP_NE_I64, false}, - {0xa0u, Opcode::V_CMP_F_I64, false}, {0xa7u, Opcode::V_CMP_T_I64, false}, - {0xb0u, Opcode::V_CMPX_F_I64, false}, {0xb1u, Opcode::V_CMPX_LT_I64, false}, - {0xb2u, Opcode::V_CMPX_EQ_I64, false}, {0xb3u, Opcode::V_CMPX_LE_I64, false}, - {0xb7u, Opcode::V_CMPX_T_I64, false}, - {0xb5u, Opcode::V_CMPX_NE_I64, false}, {0xd1u, Opcode::V_CMPX_LT_U32}, + {0xc7u, Opcode::V_CMP_T_U32}, {0xd1u, Opcode::V_CMPX_LT_U32}, {0xd2u, Opcode::V_CMPX_EQ_U32}, {0xd3u, Opcode::V_CMPX_LE_U32}, {0xd4u, Opcode::V_CMPX_GT_U32}, {0xd5u, Opcode::V_CMPX_NE_U32}, - {0xd6u, Opcode::V_CMPX_GE_U32}, {0xe1u, Opcode::V_CMP_LT_U64, false}, - {0xe0u, Opcode::V_CMP_F_U64, false}, {0xe7u, Opcode::V_CMP_T_U64, false}, - {0xf0u, Opcode::V_CMPX_F_U64, false}, {0xf1u, Opcode::V_CMPX_LT_U64, false}, - {0xf2u, Opcode::V_CMPX_EQ_U64, false}, {0xf4u, Opcode::V_CMPX_GT_U64, false}, - {0xf6u, Opcode::V_CMPX_GE_U64, false}, {0xf7u, Opcode::V_CMPX_T_U64, false}, - {0xe2u, Opcode::V_CMP_EQ_U64, false}, {0xe3u, Opcode::V_CMP_LE_U64, false}, - {0xe4u, Opcode::V_CMP_GT_U64, false}, {0xe5u, Opcode::V_CMP_NE_U64, false}, - {0xe6u, Opcode::V_CMP_GE_U64, false}, {0xf3u, Opcode::V_CMPX_LE_U64, false}, - {0xf5u, Opcode::V_CMPX_NE_U64, false}, {0xc9u, Opcode::V_CMP_LT_F16}, + {0xd6u, Opcode::V_CMPX_GE_U32}, {0xa0u, Opcode::V_CMP_F_I64, false}, + {0xa1u, Opcode::V_CMP_LT_I64, false}, {0xa2u, Opcode::V_CMP_EQ_I64, false}, + {0xa3u, Opcode::V_CMP_LE_I64, false}, {0xa4u, Opcode::V_CMP_GT_I64, false}, + {0xa5u, Opcode::V_CMP_NE_I64, false}, {0xa6u, Opcode::V_CMP_GE_I64, false}, + {0xa7u, Opcode::V_CMP_T_I64, false}, {0xb0u, Opcode::V_CMPX_F_I64, false}, + {0xb1u, Opcode::V_CMPX_LT_I64, false}, {0xb2u, Opcode::V_CMPX_EQ_I64, false}, + {0xb3u, Opcode::V_CMPX_LE_I64, false}, {0xb4u, Opcode::V_CMPX_GT_I64, false}, + {0xb5u, Opcode::V_CMPX_NE_I64, false}, {0xb6u, Opcode::V_CMPX_GE_I64, false}, + {0xb7u, Opcode::V_CMPX_T_I64, false}, {0xe0u, Opcode::V_CMP_F_U64, false}, + {0xe1u, Opcode::V_CMP_LT_U64, false}, {0xe2u, Opcode::V_CMP_EQ_U64, false}, + {0xe3u, Opcode::V_CMP_LE_U64, false}, {0xe4u, Opcode::V_CMP_GT_U64, false}, + {0xe5u, Opcode::V_CMP_NE_U64, false}, {0xe6u, Opcode::V_CMP_GE_U64, false}, + {0xe7u, Opcode::V_CMP_T_U64, false}, {0xf0u, Opcode::V_CMPX_F_U64, false}, + {0xf1u, Opcode::V_CMPX_LT_U64, false}, {0xf2u, Opcode::V_CMPX_EQ_U64, false}, + {0xf3u, Opcode::V_CMPX_LE_U64, false}, {0xf4u, Opcode::V_CMPX_GT_U64, false}, + {0xf5u, Opcode::V_CMPX_NE_U64, false}, {0xf6u, Opcode::V_CMPX_GE_U64, false}, + {0xf7u, Opcode::V_CMPX_T_U64, false}, {0xc9u, Opcode::V_CMP_LT_F16}, {0xcau, Opcode::V_CMP_EQ_F16}, {0xcbu, Opcode::V_CMP_LE_F16}, {0xccu, Opcode::V_CMP_GT_F16}, {0xcdu, Opcode::V_CMP_LG_F16}, {0xceu, Opcode::V_CMP_GE_F16}, {0xe9u, Opcode::V_CMP_NGE_F16}, @@ -1245,9 +1246,10 @@ VopcSdwaFields DecodeVopcSdwaFields(uint32_t modifier) { } bool SupportsVopcSdwa(Opcode opcode) { - // F64 SDWA operand handling is not implemented. - return opcode != Opcode::UNSUPPORTED && opcode != Opcode::V_CMP_NE_I64 && - (opcode < Opcode::V_CMP_F_F64 || opcode > Opcode::V_CMPX_TRU_F64); + // SDWA operand handling is not implemented for 64-bit compares. + return opcode != Opcode::UNSUPPORTED && + (opcode < Opcode::V_CMP_F_F64 || opcode > Opcode::V_CMPX_TRU_F64) && + (opcode < Opcode::V_CMP_F_I64 || opcode > Opcode::V_CMPX_T_U64); } void DecodeVopcSdwa(uint32_t pc, std::span code, uint32_t word_index, @@ -1628,7 +1630,9 @@ bool IsVopcCompareExec(Opcode opcode) { case Opcode::V_CMPX_LT_I64: case Opcode::V_CMPX_EQ_I64: case Opcode::V_CMPX_LE_I64: + case Opcode::V_CMPX_GT_I64: case Opcode::V_CMPX_NE_I64: + case Opcode::V_CMPX_GE_I64: case Opcode::V_CMPX_T_I64: case Opcode::V_CMPX_F_U64: case Opcode::V_CMPX_LT_U64: diff --git a/src/graphics/shader/recompiler/frontend/translate/Compare.cpp b/src/graphics/shader/recompiler/frontend/translate/Compare.cpp index aa7d2adcf..2bae195c9 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Compare.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Compare.cpp @@ -1,6 +1,7 @@ #include "graphics/shader/recompiler/frontend/translate/Translator.h" #include +#include namespace Libs::Graphics::ShaderRecompiler::Frontend { @@ -27,7 +28,8 @@ void Translator::EmitCompareConstant(const Decoder::Instruction& inst, bool valu } void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, - IR::Type type, bool scalar, bool cmpx) { + IR::Type type, bool scalar, bool cmpx, bool swap_operands) { + // The equality compares share their IR opcode with the unsigned ones. const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 || opcode == IR::ValueOpcode::SLessThanEqual64 || inst.opcode == Decoder::Opcode::V_CMP_EQ_I64 || @@ -42,8 +44,12 @@ void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueO } return ReadOperand(operand, type); }; - EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {read(inst.src0), read(inst.src1)})), scalar, - cmpx); + auto lhs = read(inst.src0); + auto rhs = read(inst.src1); + if (swap_operands) { + std::swap(lhs, rhs); + } + EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), scalar, cmpx); } void Translator::EmitInteger16Compare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index b867cd94d..6fdb98c18 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -907,26 +907,38 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_ case Decoder::Opcode::V_CMPX_NEQ_F64: case Decoder::Opcode::V_CMPX_NLT_F64: case Decoder::Opcode::V_CMPX_TRU_F64: - case Decoder::Opcode::V_CMP_EQ_I64: + case Decoder::Opcode::V_CMP_F_I64: case Decoder::Opcode::V_CMP_LT_I64: + case Decoder::Opcode::V_CMP_EQ_I64: case Decoder::Opcode::V_CMP_LE_I64: + case Decoder::Opcode::V_CMP_GT_I64: case Decoder::Opcode::V_CMP_NE_I64: + case Decoder::Opcode::V_CMP_GE_I64: + case Decoder::Opcode::V_CMP_T_I64: + case Decoder::Opcode::V_CMP_F_U64: case Decoder::Opcode::V_CMP_LT_U64: case Decoder::Opcode::V_CMP_EQ_U64: case Decoder::Opcode::V_CMP_LE_U64: case Decoder::Opcode::V_CMP_GT_U64: case Decoder::Opcode::V_CMP_NE_U64: case Decoder::Opcode::V_CMP_GE_U64: + case Decoder::Opcode::V_CMP_T_U64: + case Decoder::Opcode::V_CMPX_F_I64: case Decoder::Opcode::V_CMPX_LT_I64: case Decoder::Opcode::V_CMPX_EQ_I64: case Decoder::Opcode::V_CMPX_LE_I64: + case Decoder::Opcode::V_CMPX_GT_I64: case Decoder::Opcode::V_CMPX_NE_I64: + case Decoder::Opcode::V_CMPX_GE_I64: + case Decoder::Opcode::V_CMPX_T_I64: + case Decoder::Opcode::V_CMPX_F_U64: case Decoder::Opcode::V_CMPX_LT_U64: case Decoder::Opcode::V_CMPX_EQ_U64: case Decoder::Opcode::V_CMPX_LE_U64: case Decoder::Opcode::V_CMPX_GT_U64: case Decoder::Opcode::V_CMPX_NE_U64: case Decoder::Opcode::V_CMPX_GE_U64: + case Decoder::Opcode::V_CMPX_T_U64: include_vector(inst.src0, 2u); include_vector(inst.src1, 2u); break; diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index c0b988142..7381d788a 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -122,7 +122,7 @@ private: void EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar, bool cmpx); void EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar, bool cmpx); void EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type, - bool scalar, bool cmpx); + bool scalar, bool cmpx, bool swap_operands = false); void EmitInteger16Compare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool signed_value, bool cmpx); void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half, diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index 44872bf7b..81a27f2e5 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -145,6 +145,23 @@ void Translator::EmitVector(const Decoder::Instruction& inst) { case O::V_CMPX_LE_I64: EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true); return; + // The IR has no signed 64-bit greater-than: a > b is b < a, and a >= b is b <= a. + case O::V_CMP_GT_I64: + EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, false, + true); + return; + case O::V_CMPX_GT_I64: + EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true, + true); + return; + case O::V_CMP_GE_I64: + EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, false, + true); + return; + case O::V_CMPX_GE_I64: + EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true, + true); + return; case O::V_CMP_LE_U64: EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual64, IR::Type::U64, false, false); return; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 3c7f8c089..2e906a536 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -25704,9 +25704,10 @@ TestCase VectorCompareInteger64Edges() { const auto lhs = pairs[i][0], rhs = pairs[i][1]; const auto signed_lhs = std::bit_cast(lhs); const auto signed_rhs = std::bit_cast(rhs); - for (const auto [opcode, value] : std::array, 9>{{ + for (const auto [opcode, value] : std::array, 11>{{ {0xa0, false}, {0xa1, signed_lhs < signed_rhs}, - {0xa3, signed_lhs <= signed_rhs}, {0xa5, lhs != rhs}, {0xa7, true}, + {0xa3, signed_lhs <= signed_rhs}, {0xa4, signed_lhs > signed_rhs}, + {0xa5, lhs != rhs}, {0xa6, signed_lhs >= signed_rhs}, {0xa7, true}, {0xe0, false}, {0xe3, lhs <= rhs}, {0xe6, lhs >= rhs}, {0xe7, true}}}) { test.code.push_back(EncodeVopc(opcode, Vgpr(1), 3)); store_mask(106, value); @@ -25714,17 +25715,40 @@ TestCase VectorCompareInteger64Edges() { store_mask(20, value); } // A negative literal expands by signedness, including equality comparisons. - for (const auto [opcode, value] : std::array, 5>{{ + for (const auto [opcode, value] : std::array, 7>{{ {0xa1, -1 < signed_rhs}, {0xa3, -1 <= signed_rhs}, - {0xa5, -1 != signed_rhs}, + {0xa4, -1 > signed_rhs}, {0xa5, -1 != signed_rhs}, + {0xa6, -1 >= signed_rhs}, {0xa2, -1 == signed_rhs}, {0xe3, 0xffffffffull <= rhs}}}) { test.code.push_back(EncodeVopc(opcode, 255, 3)); test.code.push_back(0xffffffffu); store_mask(106, value); + if (opcode == 0xa4 || opcode == 0xa6) { + test.code.push_back(EncodeSop1(0x04, 10, 126)); + test.code.push_back(EncodeVopc(opcode + 0x10, 255, 3)); + test.code.push_back(0xffffffffu); + test.code.push_back(EncodeSop1(0x04, 20, 126)); + test.code.push_back(EncodeSop1(0x04, 126, 10)); + store_mask(20, value); + } + } + for (const auto [opcode, value] : std::array, 3>{{ + {0xa3, signed_lhs <= -1}, {0xa4, signed_lhs > -1}, + {0xa6, signed_lhs >= -1}}}) { + for (const u32 source : {255u, 193u}) { // Literal and inline -1. + AppendVop3(&test.code, opcode, 20, Vgpr(1), source); + if (source == 255) test.code.push_back(0xffffffffu); + store_mask(20, value); + if (opcode == 0xa4 || opcode == 0xa6) { + test.code.push_back(EncodeSop1(0x04, 10, 126)); + AppendVop3(&test.code, opcode + 0x10, 22, Vgpr(1), source); + if (source == 255) test.code.push_back(0xffffffffu); + test.code.push_back(EncodeSop1(0x04, 20, 126)); + test.code.push_back(EncodeSop1(0x04, 126, 10)); + store_mask(20, value); + } + } } - AppendVop3(&test.code, 0xa3, 20, Vgpr(1), 255); - test.code.push_back(0xffffffffu); - store_mask(20, signed_lhs <= -1); test.code.push_back(EncodeSop1(0x04, 10, 126)); AppendVop3(&test.code, 0xb2, 126, 255, Vgpr(3)); // CMPX_EQ_I64 sign-extends -1. test.code.push_back(0xffffffffu); @@ -25747,7 +25771,8 @@ TestCase VectorCompareInteger64Edges() { test.initial.resize(test.expected.size()); test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32, O::V_CMP_F_I64, O::V_CMP_LT_I64, O::V_CMP_LE_I64, O::V_CMP_EQ_I64, - O::V_CMP_NE_I64, O::V_CMP_T_I64, O::V_CMPX_EQ_I64, + O::V_CMP_GT_I64, O::V_CMP_NE_I64, O::V_CMP_GE_I64, + O::V_CMP_T_I64, O::V_CMPX_EQ_I64, O::V_CMPX_GT_I64, O::V_CMPX_GE_I64, O::V_CMP_F_U64, O::V_CMP_LE_U64, O::V_CMP_GE_U64, O::V_CMP_T_U64, O::S_MOV_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; test.required_spirv = {"OpSLessThan", "OpULessThanEqual", "OpUGreaterThanEqual"}; @@ -25772,13 +25797,16 @@ TestCase VectorCompareExecWaveMasks(u32 wave_size) { {0xc000000000000000ull, 0xbff0000000000000ull}, {0x7ff0000000000000ull, 0x7ff0000000000000ull}, {0x3ff0000000000001ull, 0x3ff0000000000000ull}}}}}; - struct Compare { u32 encoding; O opcode; u32 group; }; + struct Compare { u32 encoding; O opcode; u32 group; bool update_exec{true}; }; constexpr std::array comparisons{ Compare{0x99, O::V_CMPX_LT_I16, 0}, Compare{0x9a, O::V_CMPX_EQ_I16, 0}, Compare{0x9b, O::V_CMPX_LE_I16, 0}, Compare{0x9c, O::V_CMPX_GT_I16, 0}, Compare{0x9d, O::V_CMPX_NE_I16, 0}, Compare{0x9e, O::V_CMPX_GE_I16, 0}, Compare{0xb0, O::V_CMPX_F_I64, 1}, Compare{0xb1, O::V_CMPX_LT_I64, 1}, Compare{0xb2, O::V_CMPX_EQ_I64, 1}, Compare{0xb3, O::V_CMPX_LE_I64, 1}, + Compare{0xb4, O::V_CMPX_GT_I64, 1}, Compare{0xb6, O::V_CMPX_GE_I64, 1}, + Compare{0xa4, O::V_CMP_GT_I64, 1, false}, + Compare{0xa6, O::V_CMP_GE_I64, 1, false}, Compare{0xb7, O::V_CMPX_T_I64, 1}, Compare{0xf0, O::V_CMPX_F_U64, 1}, Compare{0xf1, O::V_CMPX_LT_U64, 1}, Compare{0xf2, O::V_CMPX_EQ_U64, 1}, Compare{0xf3, O::V_CMPX_LE_U64, 1}, Compare{0xf4, O::V_CMPX_GT_U64, 1}, @@ -25804,10 +25832,6 @@ TestCase VectorCompareExecWaveMasks(u32 wave_size) { test.expected = test.initial; auto& code = test.code; code.push_back(EncodeSop1(0x04, 10, 126)); - AppendSMovLiteral(&code, 106, vcc_lo); - AppendSMovLiteral(&code, 107, vcc_hi); - AppendSMovLiteral(&code, 22, vcc_lo); - AppendSMovLiteral(&code, 23, vcc_hi); for (const auto compare : comparisons) { for (u32 word = 0; word < 4; ++word) { code.push_back(EncodeVop2(0x1a, 30, InlineU32(2), 0)); @@ -25834,6 +25858,8 @@ TestCase VectorCompareExecWaveMasks(u32 wave_size) { case 0xb1: result = std::bit_cast(pair[0]) < std::bit_cast(pair[1]); break; case 0xb2: case 0xf2: result = pair[0] == pair[1]; break; case 0xb3: result = std::bit_cast(pair[0]) <= std::bit_cast(pair[1]); break; + case 0xa4: case 0xb4: result = std::bit_cast(pair[0]) > std::bit_cast(pair[1]); break; + case 0xa6: case 0xb6: result = std::bit_cast(pair[0]) >= std::bit_cast(pair[1]); break; case 0x3f: case 0xb7: case 0xf7: result = true; break; case 0xf1: result = pair[0] < pair[1]; break; case 0xf3: result = pair[0] <= pair[1]; break; @@ -25861,6 +25887,10 @@ TestCase VectorCompareExecWaveMasks(u32 wave_size) { expected_exec[0] &= low_exec; expected_exec[1] &= high_exec; for (const bool vop3 : {false, true}) { + AppendSMovLiteral(&code, 106, vcc_lo); + AppendSMovLiteral(&code, 107, vcc_hi); + AppendSMovLiteral(&code, 22, vcc_lo); + AppendSMovLiteral(&code, 23, vcc_hi); AppendSMovLiteral(&code, 126, low_exec); if (wave_size == 64) AppendSMovLiteral(&code, 127, high_exec); if (vop3) AppendVop3(&code, compare.encoding, 22, Vgpr(1), Vgpr(3)); @@ -25868,9 +25898,17 @@ TestCase VectorCompareExecWaveMasks(u32 wave_size) { code.push_back(EncodeSMovB32(20, 126)); code.push_back(EncodeSMovB32(21, 127)); code.push_back(EncodeSop1(0x04, 126, 10)); + const bool vcc_dest = !compare.update_exec && !vop3; + const bool sgpr_dest = !compare.update_exec && vop3; + // Wave32 writes only the low half of VCC or the selected SGPR pair. + const u32 mask_hi = wave_size == 64 ? expected_exec[1] : vcc_hi; for (const auto [reg, value] : std::array, 6>{{ - {20, expected_exec[0]}, {21, expected_exec[1]}, - {106, vcc_lo}, {107, vcc_hi}, {22, vcc_lo}, {23, vcc_hi}}}) { + {20, compare.update_exec ? expected_exec[0] : low_exec}, + {21, compare.update_exec ? expected_exec[1] : (wave_size == 64 ? high_exec : 0)}, + {106, vcc_dest ? expected_exec[0] : vcc_lo}, + {107, vcc_dest ? mask_hi : vcc_hi}, + {22, sgpr_dest ? expected_exec[0] : vcc_lo}, + {23, sgpr_dest ? mask_hi : vcc_hi}}}) { AppendStoreSgprAtLaneDwordOffset(&code, reg, 0, u32(test.expected.size())); test.expected.insert(test.expected.end(), wave_size, value); } @@ -42662,11 +42700,19 @@ int main(int argc, char **argv) { vulkan.CheckNativeIndirectDispatch(); return 0; } - if (argc == 2 && std::strcmp(argv[1], "--integer64-compare-only") == 0) { + if (argc == 2 && (std::strcmp(argv[1], "--integer64-compare-only") == 0 || + std::strcmp(argv[1], "--cmp-i64-only") == 0)) { VulkanHarness vulkan; RunCase(&vulkan, VectorCompareInteger64Edges()); RunCase(&vulkan, VectorCompareExecWaveMasks(32)); RunCase(&vulkan, VectorCompareExecWaveMasks(64)); + RunCase(&vulkan, VectorCompare64WaveMasks(32, true)); + RunCase(&vulkan, VectorCompare64WaveMasks(64, true)); + RunCase(&vulkan, VectorVop3CompareEqI64OnGpu()); + RunCase(&vulkan, VectorVop3CompareEqU64OnGpu()); + RunCase(&vulkan, VectorVop3CompareGtU64OnGpu()); + RunCase(&vulkan, VectorVopcCompareLtU64OnGpu()); + RunCase(&vulkan, VectorVop3CompareNeU64OnGpu()); RunCase(&vulkan, VectorVopcCmpxNeU64CapturedExecMask()); RunCase(&vulkan, VectorVop3CmpxNeI64CapturedExecMask()); return 0; diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 11f2d8c8f..2ff46bc9a 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -4823,34 +4823,92 @@ void TestNewShaderDecoderArchitecture() { "VOP2 DPP V_PK_FMAC_F16 lost its implicit packed modifiers"); } -void TestNewShaderRecompilerRejectsDppOn64BitCompares() { - const uint32_t opcodes[] = { - 0xa2u, 0xa5u, 0xb5u, 0xe2u, 0xe4u, 0xe5u, - 0xf5u}; // eq_i64, ne_i64, cmpx_ne_i64, eq_u64, gt_u64, ne_u64, cmpx_ne_u64 - for (const auto opcode : opcodes) { - const uint32_t shader[] = { - EncodeVopc(opcode, 250u, 0u), // DPP escape in SRC0 - EncodeVop2Dpp(0u), - 0xbf810000u, - }; - - ShaderRecompiler::Decoder::Program program; - ShaderRecompiler::Decoder::DecodeProgram(shader, program); - Check(program.instructions.size() == 2u, - "64-bit VOPC DPP decode did not consume its modifier word"); - const auto &compare = program.instructions.front(); - Check(compare.opcode == ShaderRecompiler::Decoder::Opcode::UNSUPPORTED, - "64-bit VOPC illegally accepted a DPP modifier"); - Check((compare.unsupported_reason.find("VOPC DPP modifier is not supported for opcode") != std::string::npos), - "64-bit VOPC DPP rejection reason was not explicit"); +void TestNewShaderRecompilerInteger64CompareEncodings() { + namespace D = ShaderRecompiler::Decoder; + using O = D::Opcode; + const O families[][8] = { + {O::V_CMP_F_I64, O::V_CMP_LT_I64, O::V_CMP_EQ_I64, O::V_CMP_LE_I64, + O::V_CMP_GT_I64, O::V_CMP_NE_I64, O::V_CMP_GE_I64, O::V_CMP_T_I64}, + {O::V_CMPX_F_I64, O::V_CMPX_LT_I64, O::V_CMPX_EQ_I64, O::V_CMPX_LE_I64, + O::V_CMPX_GT_I64, O::V_CMPX_NE_I64, O::V_CMPX_GE_I64, O::V_CMPX_T_I64}, + {O::V_CMP_F_U64, O::V_CMP_LT_U64, O::V_CMP_EQ_U64, O::V_CMP_LE_U64, + O::V_CMP_GT_U64, O::V_CMP_NE_U64, O::V_CMP_GE_U64, O::V_CMP_T_U64}, + {O::V_CMPX_F_U64, O::V_CMPX_LT_U64, O::V_CMPX_EQ_U64, O::V_CMPX_LE_U64, + O::V_CMPX_GT_U64, O::V_CMPX_NE_U64, O::V_CMPX_GE_U64, O::V_CMPX_T_U64}}; + const uint32_t bases[] = {0xa0u, 0xb0u, 0xe0u, 0xf0u}; + for (uint32_t family = 0; family < std::size(families); ++family) { + const bool update_exec = (family % 2) != 0; + for (uint32_t predicate = 0; predicate < 8; ++predicate) { + const auto encoding = bases[family] + predicate; + for (const bool vop3 : {false, true}) { + const std::array words = vop3 + ? std::array{EncodeVop3Word0(encoding, 20), + EncodeVop3Word1(257, 259, 0)} + : std::array{EncodeVopc(encoding, 257, 3), 0xbf810000u}; + D::Instruction compare; + D::DecodeInstruction(words, 0, compare); + const auto destination = update_exec ? D::OperandKind::ExecLo + : vop3 ? D::OperandKind::Sgpr : D::OperandKind::VccLo; + Check(compare.family == (vop3 ? D::Family::VOP3 : D::Family::VOPC) && + compare.opcode == families[family][predicate] && + compare.opcode_id == encoding && + compare.word_count == (vop3 ? 2u : 1u) && + compare.dst.kind == destination && + (!vop3 || update_exec || compare.dst.reg == 20) && + compare.src_count == 2 && + compare.src0.kind == D::OperandKind::Vgpr && compare.src0.reg == 1 && + compare.src1.kind == D::OperandKind::Vgpr && compare.src1.reg == 3, + "64-bit integer compare decoded incorrectly"); + for (const bool high_src0 : {false, true}) { + // Only the compare's pair width makes v13 reachable by MOVRELD, + // including predicates F/T that never read their operands. + const uint32_t src0 = high_src0 ? 268u : 266u; + const uint32_t src1 = high_src0 ? 10u : 12u; + std::vector shader{EncodeVop1(0x42, 12, 256)}; + if (vop3) { + shader.push_back(EncodeVop3Word0(encoding, 20)); + shader.push_back(EncodeVop3Word1(src0, src1 + 256, 0)); + } else { + shader.push_back(EncodeVopc(encoding, src0, src1)); + } + shader.push_back(EncodeSopp(0x01)); + D::Program decoded; + D::DecodeProgram(shader, decoded); + ShaderComputeInputInfo compute{}; + ShaderRecompiler::Frontend::TranslateOptions options{.stage = ShaderType::Compute}; + options.input_info.compute = &compute; + const auto program = ShaderRecompiler::Frontend::TranslateProgram( + decoded, ShaderRecompiler::CFG::BuildGraph(decoded), options); + bool includes_high_word = false; + for (const auto *block : program.blocks) { + for (const auto &inst : *block) { + includes_high_word |= + inst.GetOpcode() == ShaderRecompiler::IR::ValueOpcode::SetVectorRegister && + ShaderRecompiler::IR::RegIndex(inst.Arg(0).VectorRegister()) == 13; + } + } + Check(includes_high_word, "64-bit compare omitted a VGPR pair's high word"); + } + } + for (const bool dpp : {false, true}) { + const uint32_t shader[] = { + EncodeVopc(encoding, dpp ? 250u : 249u, 3), + dpp ? EncodeVop2Dpp(1) : EncodeVopcSdwa(1), + 0xbf810000u}; + D::Program program; + D::DecodeProgram(shader, program); + Check(program.instructions.size() == 2u && + program.instructions.back().opcode == O::S_ENDPGM, + "64-bit VOPC decode did not consume its modifier word"); + const auto &compare = program.instructions.front(); + const auto reason = dpp ? "VOPC DPP modifier is not supported for opcode" + : "VOPC SDWA modifier is not supported for opcode"; + Check(compare.opcode == O::UNSUPPORTED && compare.word_count == 2u && + compare.unsupported_reason.find(reason) != std::string::npos, + "64-bit VOPC did not explicitly reject an illegal modifier"); + } + } } - const uint32_t sdwa[] = {EncodeVopc(0xa5u, 249u, 0u), 0x06060000u}; - ShaderRecompiler::Decoder::Instruction compare; - ShaderRecompiler::Decoder::DecodeInstruction(sdwa, 0u, compare); - Check(compare.opcode == ShaderRecompiler::Decoder::Opcode::UNSUPPORTED && - compare.word_count == 2u && - compare.unsupported_reason.find("VOPC SDWA modifier is not supported") != std::string::npos, - "V_CMP_NE_I64 accepted an illegal SDWA encoding"); } void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() { @@ -15551,7 +15609,7 @@ int main() { TestVopcCmpxClassF16Decoder(); TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16(); TestNewShaderRecompilerIrLookupMissFailsExplicitly(); - TestNewShaderRecompilerRejectsDppOn64BitCompares(); + TestNewShaderRecompilerInteger64CompareEncodings(); TestFloatComparisonInputModes(); TestPsInputCountRegisterDecode(); TestPixelAncillaryLayerInput();