From 7a0e1dafd756e4e7714ee1cf3397ce38ca779b95 Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 5 Oct 2026 15:31:47 +0400 Subject: [PATCH] shader: classify native half values with shared operand decoding --- .../backend/spirv/spirvEmitterAluHelpers.cpp | 27 +++-- .../backend/spirv/spirvEmitterInstructions.h | 1 + .../backend/spirv/spirvEmitterInternal.h | 1 + .../frontend/decode/ShaderDecoder.h | 1 + .../frontend/decode/VectorAluOps.cpp | 3 + .../recompiler/frontend/translate/Compare.cpp | 12 ++- .../recompiler/frontend/translate/Float.cpp | 8 +- .../frontend/translate/Translate.cpp | 89 ++++++----------- .../frontend/translate/Translator.h | 5 +- .../recompiler/frontend/translate/Vector.cpp | 1 + .../recompiler/ir/opcodes/ValueOpcodes.inc | 1 + tests/ShaderRecompilerComputeTests.cpp | 99 ++++++++++++++++++- tests/shaderCfgTests.cpp | 41 ++++++++ 13 files changed, 208 insertions(+), 81 deletions(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAluHelpers.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAluHelpers.cpp index 1ccb434b..b35f8a8e 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAluHelpers.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAluHelpers.cpp @@ -125,20 +125,23 @@ uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit, return EmitLogicalAndBool(state, selected, class_match); } -uint32_t EmitClassMaskF32(EmitterState& state, uint32_t value, uint32_t mask) { - const auto bits = EmitBitcastF32ToU32(state, value); - const auto sign_bits = EmitAndConstant(state, bits, 0x80000000u); - const auto abs_bits = EmitAndConstant(state, bits, 0x7fffffffu); - const auto exponent_bits = EmitAndConstant(state, abs_bits, 0x7f800000u); - const auto mantissa_bits = EmitAndConstant(state, abs_bits, 0x007fffffu); - const auto quiet_bits = EmitAndConstant(state, mantissa_bits, 0x00400000u); +static uint32_t EmitClassMaskBits(EmitterState& state, uint32_t bits, uint32_t mask, bool half) { + const uint32_t sign_mask = half ? 0x8000u : 0x80000000u; + const uint32_t exponent_mask = half ? 0x7c00u : 0x7f800000u; + const uint32_t mantissa_mask = half ? 0x03ffu : 0x007fffffu; + const uint32_t quiet_mask = half ? 0x0200u : 0x00400000u; + const auto sign_bits = EmitAndConstant(state, bits, sign_mask); + const auto abs_bits = EmitAndConstant(state, bits, sign_mask - 1u); + const auto exponent_bits = EmitAndConstant(state, abs_bits, exponent_mask); + const auto mantissa_bits = EmitAndConstant(state, abs_bits, mantissa_mask); + const auto quiet_bits = EmitAndConstant(state, mantissa_bits, quiet_mask); const auto sign = EmitCompareU32Constant(state, spv::OpINotEqual, sign_bits, 0); const auto positive = EmitLogicalNotBool(state, sign); const auto exponent_zero = EmitCompareU32Constant(state, spv::OpIEqual, exponent_bits, 0); const auto exponent_nonzero = EmitLogicalNotBool(state, exponent_zero); const auto exponent_inf = - EmitCompareU32Constant(state, spv::OpIEqual, exponent_bits, 0x7f800000u); + EmitCompareU32Constant(state, spv::OpIEqual, exponent_bits, exponent_mask); const auto finite_exponent = EmitLogicalNotBool(state, exponent_inf); const auto mantissa_zero = EmitCompareU32Constant(state, spv::OpIEqual, mantissa_bits, 0); const auto mantissa_nonzero = EmitLogicalNotBool(state, mantissa_zero); @@ -178,6 +181,14 @@ uint32_t EmitClassMaskF32(EmitterState& state, uint32_t value, uint32_t mask) { EmitClassMaskBitMatch(state, mask, 9, EmitLogicalAndBool(state, inf, positive))); } +uint32_t EmitClassMaskF32(EmitterState& state, uint32_t value, uint32_t mask) { + return EmitClassMaskBits(state, EmitBitcastF32ToU32(state, value), mask, false); +} + +uint32_t EmitClassMaskF16(EmitterState& state, uint32_t bits, uint32_t mask) { + return EmitClassMaskBits(state, bits, mask, true); +} + uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) { const auto lhs_class = EmitClassifyF32(state, lhs); const auto rhs_class = EmitClassifyF32(state, rhs); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h index c2671c9c..e3d3a815 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h @@ -152,6 +152,7 @@ inline constexpr auto EmitFPOrdGreaterThanEqual32 = EmitFloatCompare32; uint32_t EmitFPIsNan32(EmitterState& state, uint32_t arg0); inline constexpr auto EmitFPCmpClass32 = EmitClassMaskF32; +inline constexpr auto EmitFPCmpClass16 = EmitClassMaskF16; EMIT_NATIVE(FPAdd32, OpFAdd, F32, uint32_t, uint32_t) EMIT_NATIVE(FPSub32, OpFSub, F32, uint32_t, uint32_t) EMIT_NATIVE(FPMul32, OpFMul, F32, uint32_t, uint32_t) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index f16ba9da..b3c4057e 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -510,6 +510,7 @@ uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit, uint32_t class_match); uint32_t EmitClassMaskF32(EmitterState& state, uint32_t value, uint32_t mask); +uint32_t EmitClassMaskF16(EmitterState& state, uint32_t bits, uint32_t mask); uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value); diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 873eee42..4a2be8fd 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -397,6 +397,7 @@ enum class Opcode { V_CMP_T_I32, V_CMP_CLASS_F32, V_CMPX_CLASS_F32, + V_CMPX_CLASS_F16, V_CMP_LT_I16, V_CMP_EQ_I16, V_CMP_LE_I16, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index e8822614..8e9b4daa 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -252,6 +252,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = { {0x99u, Opcode::V_CMPX_LT_I16, false}, {0x9au, Opcode::V_CMPX_EQ_I16, false}, {0x9bu, Opcode::V_CMPX_LE_I16, false}, {0x9cu, Opcode::V_CMPX_GT_I16, false}, {0x9du, Opcode::V_CMPX_NE_I16, false}, {0x9eu, Opcode::V_CMPX_GE_I16, false}, + {0x9fu, Opcode::V_CMPX_CLASS_F16, false}, {0xa9u, Opcode::V_CMP_LT_U16}, {0xaau, Opcode::V_CMP_EQ_U16}, {0xabu, Opcode::V_CMP_LE_U16}, {0xacu, Opcode::V_CMP_GT_U16}, {0xadu, Opcode::V_CMP_NE_U16}, {0xaeu, Opcode::V_CMP_GE_U16}, @@ -917,6 +918,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) { case Opcode::V_CMPX_NEQ_F16: case Opcode::V_CMPX_NLT_F16: case Opcode::V_CMP_CLASS_F32: + case Opcode::V_CMPX_CLASS_F16: case Opcode::V_CMPX_CLASS_F32: return true; default: return false; } @@ -1540,6 +1542,7 @@ bool IsVopcCompareExec(Opcode opcode) { case Opcode::V_CMPX_NE_I32: case Opcode::V_CMPX_GE_I32: case Opcode::V_CMPX_CLASS_F32: + case Opcode::V_CMPX_CLASS_F16: case Opcode::V_CMPX_LT_U32: case Opcode::V_CMPX_EQ_U32: case Opcode::V_CMPX_LE_U32: diff --git a/src/graphics/shader/recompiler/frontend/translate/Compare.cpp b/src/graphics/shader/recompiler/frontend/translate/Compare.cpp index be2135a6..3b4bee2c 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Compare.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Compare.cpp @@ -72,11 +72,13 @@ void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false, cmpx); } -void Translator::EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx) { - const auto value = ReadOperand(inst.src0, IR::Type::F32); - const auto mask = ReadOperand(inst.src1, IR::Type::U32); - EmitCompareResult(inst, IR::U1(ir.Emit(IR::ValueOpcode::FPCmpClass32, {value, mask})), false, - cmpx); +void Translator::EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx, bool half) { + const auto value = half ? IR::Value(Read16LaneBits(inst.src0, false)) + : ReadOperand(inst.src0, IR::Type::F32); + const auto mask = half ? IR::Value(Read16LaneBits(inst.src1, false)) + : ReadOperand(inst.src1, IR::Type::U32); + const auto opcode = half ? IR::ValueOpcode::FPCmpClass16 : IR::ValueOpcode::FPCmpClass32; + EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {value, mask})), false, cmpx); } } // namespace Libs::Graphics::ShaderRecompiler::Frontend diff --git a/src/graphics/shader/recompiler/frontend/translate/Float.cpp b/src/graphics/shader/recompiler/frontend/translate/Float.cpp index 4ad5ef33..e88ddc5b 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Float.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Float.cpp @@ -8,13 +8,13 @@ namespace Libs::Graphics::ShaderRecompiler::Frontend { void Translator::PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator, bool quiet_snan) { const auto translate_lane = [&](bool high) { - const auto lhs = ReadF16LaneAsF32(inst.src0, high, true); - const auto rhs = ReadF16LaneAsF32(inst.src1, high, true); + const auto lhs = ReadF16LaneAsF32(inst.src0, high); + const auto rhs = ReadF16LaneAsF32(inst.src1, high); IR::F32 result; if (accumulator) { - result = IR::F32(ir.Emit(opcode, {lhs, rhs, ReadF16LaneAsF32(inst.dst, high, true)})); + result = IR::F32(ir.Emit(opcode, {lhs, rhs, ReadF16LaneAsF32(inst.dst, high)})); } else if (inst.src_count == 3u) { - result = IR::F32(ir.Emit(opcode, {lhs, rhs, ReadF16LaneAsF32(inst.src2, high, true)})); + result = IR::F32(ir.Emit(opcode, {lhs, rhs, ReadF16LaneAsF32(inst.src2, high)})); } else { result = IR::F32(ir.Emit(opcode, {lhs, rhs})); } diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index 14e748e9..27fb25bf 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -503,41 +503,11 @@ IR::U64 Translator::ReadU64(const Decoder::Operand& operand) { return IR::U64(ReadOperand(operand, IR::Type::U64)); } -IR::F32 Translator::ReadF16LaneAsF32(const Decoder::Operand& operand, bool high_lane, bool packed) { - if (operand.kind == Decoder::OperandKind::FloatInlineConstant) { - const bool use_zero = packed && (high_lane ? operand.op_sel_hi : operand.op_sel); - auto value = use_zero ? IR::F32(IR::Value::F32(0.0f)) - : ir.BitCastF32(IR::U32(IR::Value(operand.value))); - const auto half = IR::F16(ir.Emit(IR::ValueOpcode::ConvertF16F32, {value})); - value = IR::F32(ir.Emit(IR::ValueOpcode::ConvertF32F16, {half})); - if (operand.absolute) { - value = IR::F32(ir.Emit(IR::ValueOpcode::FPAbs32, {value})); - } - if (high_lane ? operand.negate_hi : operand.negate) { - value = IR::F32(ir.Emit(IR::ValueOpcode::FPNeg32, {value})); - } - return value; - } - auto raw_operand = operand; - raw_operand.sdwa_sel = 6; - raw_operand.sdwa_sext = false; - const auto bits = ApplyBitSourceModifiers(raw_operand, ReadRawU32(operand)); - uint32_t offset = (high_lane ? operand.op_sel_hi : operand.op_sel) ? 16u : 0u; - if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) { - offset = operand.sdwa_sel == 5u ? 16u : 0u; - } - const auto half_u32 = IR::U32( - ir.Emit(IR::ValueOpcode::BitFieldUExtract, {bits, IR::Value(offset), IR::Value(16u)})); - const auto half_u16 = IR::U16(ir.Emit(IR::ValueOpcode::ConvertU16U32, {half_u32})); +IR::F32 Translator::ReadF16LaneAsF32(const Decoder::Operand& operand, bool high_lane) { + const auto bits = Read16LaneBits(operand, high_lane); + const auto half_u16 = IR::U16(ir.Emit(IR::ValueOpcode::ConvertU16U32, {bits})); const auto half = IR::F16(ir.Emit(IR::ValueOpcode::BitCastF16U16, {half_u16})); - auto value = IR::F32(ir.Emit(IR::ValueOpcode::ConvertF32F16, {half})); - if (operand.absolute) { - value = IR::F32(ir.Emit(IR::ValueOpcode::FPAbs32, {value})); - } - if (high_lane ? operand.negate_hi : operand.negate) { - value = IR::F32(ir.Emit(IR::ValueOpcode::FPNeg32, {value})); - } - return value; + return IR::F32(ir.Emit(IR::ValueOpcode::ConvertF32F16, {half})); } IR::F32 Translator::ReadF16AsF32(const Decoder::Operand& operand) { @@ -555,29 +525,6 @@ IR::F32 Translator::ReadMixF32(const Decoder::Operand& operand) { return IR::F32(ReadOperand(value_operand, IR::Type::F32)); } -IR::U32 Translator::ReadU16LaneRaw(const Decoder::Operand& operand, bool high_lane) { - auto raw_operand = operand; - raw_operand.sdwa_sel = 6; - raw_operand.sdwa_sext = false; - const auto bits = ApplyBitSourceModifiers(raw_operand, ReadRawU32(operand)); - uint32_t offset = (high_lane ? operand.op_sel_hi : operand.op_sel) ? 16u : 0u; - uint32_t width = 16u; - if (operand.sdwa_sel <= 3u) { - offset = operand.sdwa_sel * 8u; - width = 8u; - } else if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) { - offset = operand.sdwa_sel == 5u ? 16u : 0u; - } - if (width == 8u && operand.sdwa_sext) { - // Extend the selected byte into the 16-bit operand before halfword modifiers. - const auto value = IR::U32( - ir.Emit(IR::ValueOpcode::BitFieldSExtract, {bits, IR::Value(offset), IR::Value(width)})); - return ir.BitwiseAnd(value, IR::U32(IR::Value(0xffffu))); - } - return IR::U32( - ir.Emit(IR::ValueOpcode::BitFieldUExtract, {bits, IR::Value(offset), IR::Value(width)})); -} - IR::U32 Translator::ReadU16LaneAsU32(const Decoder::Operand& operand, bool high_lane, bool sign_extend) { auto value = Read16LaneBits(operand, high_lane); @@ -593,12 +540,36 @@ IR::U32 Translator::ReadU16AsU32(const Decoder::Operand& operand, bool sign_exte } IR::U32 Translator::Read16LaneBits(const Decoder::Operand& operand, bool high_lane) { - auto value = ReadU16LaneRaw(operand, high_lane); + auto raw_operand = operand; + raw_operand.sdwa_sel = 6; + raw_operand.sdwa_sext = false; + auto bits = ReadRawU32(operand); + if (operand.kind == Decoder::OperandKind::FloatInlineConstant) { + // Inline floats encode the operand's width before SDWA or OP_SEL selects bits. + const auto half = IR::F16(ir.Emit(IR::ValueOpcode::ConvertF16F32, {ir.BitCastF32(bits)})); + const auto half_bits = IR::U16(ir.Emit(IR::ValueOpcode::BitCastU16F16, {half})); + bits = IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {half_bits})); + } + bits = ApplyBitSourceModifiers(raw_operand, bits); + uint32_t offset = (high_lane ? operand.op_sel_hi : operand.op_sel) ? 16u : 0u; + uint32_t width = 16u; + if (operand.sdwa_sel <= 3u) { + offset = operand.sdwa_sel * 8u; + width = 8u; + } else if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) { + offset = operand.sdwa_sel == 5u ? 16u : 0u; + } + const auto extract = width == 8u && operand.sdwa_sext + ? IR::ValueOpcode::BitFieldSExtract + : IR::ValueOpcode::BitFieldUExtract; + auto value = IR::U32(ir.Emit(extract, {bits, IR::Value(offset), IR::Value(width)})); + if (width == 8u && operand.sdwa_sext) { + value = ir.BitwiseAnd(value, IR::U32(IR::Value(0xffffu))); + } if (operand.absolute) { value = ir.BitwiseAnd(value, IR::U32(IR::Value(0x7fffu))); } if (high_lane ? operand.negate_hi : operand.negate) { - // RDNA2 source NEG flips the sign bit, including packed integer operands. value = ir.BitwiseXor(value, IR::U32(IR::Value(0x8000u))); } return value; diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index ab6ca393..5efaa478 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -40,10 +40,9 @@ private: IR::U32 ReadU32(const Decoder::Operand& operand); std::array ReadU32Pair(const Decoder::Operand& operand); IR::U64 ReadU64(const Decoder::Operand& operand); - IR::F32 ReadF16LaneAsF32(const Decoder::Operand& operand, bool high_lane, bool packed = false); + IR::F32 ReadF16LaneAsF32(const Decoder::Operand& operand, bool high_lane); IR::F32 ReadF16AsF32(const Decoder::Operand& operand); IR::F32 ReadMixF32(const Decoder::Operand& operand); - IR::U32 ReadU16LaneRaw(const Decoder::Operand& operand, bool high_lane); IR::U32 ReadU16LaneAsU32(const Decoder::Operand& operand, bool high_lane, bool sign_extend); IR::U32 ReadU16AsU32(const Decoder::Operand& operand, bool sign_extend); IR::U32 Read16LaneBits(const Decoder::Operand& operand, bool high_lane); @@ -127,7 +126,7 @@ private: void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half, bool cmpx); void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx); - void EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx); + void EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx, bool half = false); void V_CVT_F32_UBYTE(const Decoder::Instruction& inst, uint32_t byte_index); void V_CVT_F32_U32(const Decoder::Instruction& inst); void V_CVT_F32_I32(const Decoder::Instruction& inst); diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index fa85c7b4..b3914e68 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -346,6 +346,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) { case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true, true); return; case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return; case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return; + case O::V_CMPX_CLASS_F16: EmitFloatClassCompare(inst, true, true); return; case O::V_CVT_F32_UBYTE0: V_CVT_F32_UBYTE(inst, 0); return; case O::V_CVT_F32_UBYTE1: V_CVT_F32_UBYTE(inst, 1); return; diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc index 67c310ee..b95d2917 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc @@ -175,6 +175,7 @@ VALUE_OPCODE(FPOrdGreaterThanEqual32, U1, F32, F32) VALUE_OPCODE(FPUnordGreaterThanEqual32, U1, F32, F32) VALUE_OPCODE(FPIsNan32, U1, F32) VALUE_OPCODE(FPCmpClass32, U1, F32, U32) +VALUE_OPCODE(FPCmpClass16, U1, U32, U32) VALUE_OPCODE(FPAdd32, F32, F32, F32) VALUE_OPCODE(FPSub32, F32, F32, F32) diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index c5479c93..b3eceda5 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -18598,6 +18598,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, case Opcode::V_CMPX_NLT_F32: case Opcode::V_CMP_CLASS_F32: case Opcode::V_CMPX_CLASS_F32: + case Opcode::V_CMPX_CLASS_F16: case Opcode::V_CMP_LT_F16: case Opcode::V_CMP_EQ_F16: case Opcode::V_CMP_LE_F16: @@ -23907,23 +23908,33 @@ TestCase FloatInlineConstantF16UsesNumericValue() { AppendVMovLiteral(&code, 2, 0x3c003c00u); // 1.0h, 1.0h AppendVMovLiteral(&code, 10, 0xaaaa0000u); AppendVMovLiteral(&code, 12, 0xbbbb0000u); + AppendVMovLiteral(&code, 14, 0xcccc0000u); + AppendVMovLiteral(&code, 15, 0xdddd0000u); AppendVop3(&code, 0x34bu, 10, 246u, Vgpr(1), Vgpr(2)); AppendVop3p(&code, 0x0eu, 11, 246u, Vgpr(1), Vgpr(2), 0x1u); AppendVop3(&code, 0x34bu, 12, 248u, 242u, 128u); + AppendVop3(&code, 0x311u, 13, 242u, 248u); + AppendVop3(&code, 0x34bu, 14, 246u, Vgpr(1), Vgpr(2), 0, 1); + AppendVop3(&code, 0x303u, 15, 240u, InlineU32(1)); AppendStoreVgpr(&code, 10, 0); AppendStoreVgpr(&code, 11, 1); AppendStoreVgpr(&code, 12, 2); + AppendStoreVgpr(&code, 13, 3); + AppendStoreVgpr(&code, 14, 4); + AppendStoreVgpr(&code, 15, 5); AppendEnd(&code); TestCase test; test.name = "FloatInlineConstantF16UsesNumericValue"; test.code = std::move(code); - test.expected = {0xaaaa4200u, 0x3c004200u, 0xbbbb3118u}; + test.expected = {0xaaaa4200u, 0x3c004200u, 0xbbbb3118u, + 0x31183c00u, 0xcccc3c00u, 0xdddd3801u}; test.opcodes = {O::V_MOV_B32, O::V_FMA_F16, O::V_PK_FMA_F16, + O::V_PACK_B32_F16, O::V_ADD_NC_U16, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; - test.decoded_counts = {{"4.000000", 2}, {"0.159155", 1}}; + test.decoded_counts = {{"4.000000", 3}, {"0.159155", 2}}; return test; } @@ -25676,6 +25687,77 @@ TestCase VectorVopcSdwaCmpxClassF32CapturedExecMask() { return test; } +TestCase VectorCmpxClassF16(u32 wave_size) { + using O = ShaderOpcode; + // One raw half value for each architectural class bit, in bit order. + constexpr std::array values{ + 0x7c01u, 0x7e01u, 0xfc00u, 0xbc00u, 0x8001u, + 0x8000u, 0x0000u, 0x0001u, 0x3c00u, 0x7c00u}; + TestCase test; + test.name = wave_size == 64 ? "VectorCmpxClassF16Wave64" : "VectorCmpxClassF16Wave32"; + for (const auto value : values) { + test.initial.insert(test.initial.end(), {0xa55a0000u | value, (value << 16u) | 0x7e01u}); + } + test.expected = test.initial; + auto &code = test.code; + AppendSMovLiteral(&code, 106, 0x12345678u); + AppendSMovLiteral(&code, 107, 0x87654321u); + code.push_back(EncodeSMovB32(127, InlineU32(0))); + const auto compare = [&](std::initializer_list words, u32 expected) { + code.insert(code.end(), words); + code.push_back(EncodeSMovB32(20, 126)); + code.push_back(EncodeSMovB32(21, 127)); + code.push_back(EncodeSMovB32(126, InlineU32(1))); + AppendStoreSgpr(&code, 20, static_cast(test.expected.size())); + AppendStoreSgpr(&code, 21, static_cast(test.expected.size()) + 1u); + test.expected.insert(test.expected.end(), {expected, 0}); + }; + for (u32 index = 0; index < values.size(); ++index) { + AppendVMovU32(&code, 30, index * 8u); + AppendBufferLoadDword(&code, 0, 30); + AppendVMovU32(&code, 30, index * 8u + 4u); + AppendBufferLoadDword(&code, 2, 30); + for (u32 bit = 0; bit < 12; ++bit) { + AppendVMovU32(&code, 1, bit == 11 ? 0 : 1u << bit); + const u32 expected = bit == index ? 1 : 0; + if ((bit & 1u) == 0) { + compare({EncodeVopc(0x9f, Vgpr(0), 1)}, expected); + } else { + compare({EncodeVop3Word0(0x9f, 126), EncodeVop3Word1(Vgpr(0), Vgpr(1))}, expected); + } + } + const u32 finite = index >= 3 && index <= 8 ? 1 : 0; + compare({0x7d3f70f9u, 0x86360000u}, finite); // Captured -abs(v0), inline56. + compare({0x7d3f70f9u, 0x86350002u}, finite); // Captured -abs(v2.word1). + } + AppendVMovU32(&code, 1, 1u << 8u); + compare({EncodeVopc(0x9f, 242, 1)}, 1); // Inline1.0 encodes half0x3c00. + AppendVMovU32(&code, 1, 1u << 6u); + compare({EncodeVopc(0x9f, 249, 1), + EncodeVopcSdwa(242, 0, 0, 5, 6, 0, 0, 0, 0, 0, 0, 1)}, 1); + AppendVMovU32(&code, 1, 1u << 7u); + compare({EncodeVopc(0x9f, 249, 1), + EncodeVopcSdwa(242, 0, 0, 1, 6, 0, 0, 0, 0, 0, 0, 1)}, 1); + AppendVMovU32(&code, 30, 8u * 8u); + AppendBufferLoadDword(&code, 0, 30); + compare({EncodeVopc(0x9f, 249, 248), + EncodeVopcSdwa(0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 1)}, 1); + // INV_2PI as a half class mask is0x3118, including positive normal bit8. + AppendVMovU32(&code, 1, 1u << 8u); + code.push_back(EncodeSMovB32(126, InlineU32(0))); + compare({EncodeVopc(0x9f, Vgpr(0), 1)}, 0); + AppendStoreSgpr(&code, 106, static_cast(test.expected.size())); + AppendStoreSgpr(&code, 107, static_cast(test.expected.size()) + 1u); + test.expected.insert(test.expected.end(), {0x12345678u, 0x87654321u}); + AppendEnd(&code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD, + O::V_CMPX_CLASS_F16, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.compute_info.wave_size = wave_size; + test.has_compute_info = true; + return test; +} + TestCase Wave32VccMasksPreserveOtherHalf() { using O = ShaderOpcode; TestCase test; @@ -34763,6 +34845,8 @@ std::vector MakeCases() { AddCase(VectorVop3CmpxNeI64CapturedExecMask); AddCase(VectorCompareClassF32); AddCase(VectorVopcSdwaCmpxClassF32CapturedExecMask); + cases.push_back(VectorCmpxClassF16(32)); + cases.push_back(VectorCmpxClassF16(64)); AddCase(VectorCompareF16Ops); AddCase(Wave32VccMasksPreserveOtherHalf); AddCase(Vop2SdwaCndmaskSourceModifier); @@ -40105,6 +40189,17 @@ int main(int argc, char **argv) { RunCase(&vulkan, VectorVopcSdwaCmpxClassF32CapturedExecMask()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--cmpx-class-f16-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, VectorCmpxClassF16(32)); + RunCase(&vulkan, VectorCmpxClassF16(64)); + RunCase(&vulkan, VectorCompareClassF32()); + RunCase(&vulkan, VectorVopcSdwaCmpxClassF32CapturedExecMask()); + RunCase(&vulkan, FloatInlineConstantF16UsesNumericValue()); + RunCase(&vulkan, VectorCompareF16Ops()); + RunCase(&vulkan, VectorMinMaxF16Ops()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--cmp-eq-f64-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, VectorCompareF64Edges()); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 36f5300f..be564030 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -4894,6 +4894,46 @@ void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() { CheckSpirvBinaryValidates(result.spirv); } +void TestVopcCmpxClassF16Decoder() { + using namespace ShaderRecompiler::Decoder; + for (const uint32_t modifier : {0x86360000u, 0x86350002u}) { + const uint32_t captured[] = {0x7d3f70f9u, modifier}; + Instruction decoded; + DecodeInstruction(captured, 0, decoded); + Check(decoded.opcode == Opcode::V_CMPX_CLASS_F16 && decoded.word_count == 2 && + decoded.dst.kind == OperandKind::ExecLo && + decoded.src0.kind == OperandKind::Vgpr && + decoded.src0.reg == (modifier == 0x86360000u ? 0u : 2u) && + decoded.src0.sdwa_sel == (modifier == 0x86360000u ? 6u : 5u) && + decoded.src0.absolute && decoded.src0.negate && + decoded.src1.kind == OperandKind::IntegerInlineConstant && + decoded.src1.value == 56 && decoded.src1.sdwa_sel == 6, + "captured V_CMPX_CLASS_F16 SDWA fields are incorrect"); + const uint32_t shader[] = {captured[0], captured[1], EncodeSopp(0x01)}; + auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Pixel)); + CheckSpirvBinaryValidates(result.spirv); + } + const uint32_t compact[] = {EncodeVopc(0x9f, 256, 1)}; + const uint32_t vop3[] = {EncodeVop3Word0(0x9f, 126), + EncodeVop3Word1(256, 257, 0)}; + for (auto words : {std::span(compact), + std::span(vop3)}) { + Instruction decoded; + DecodeInstruction(words, 0, decoded); + Check(decoded.opcode == Opcode::V_CMPX_CLASS_F16 && + decoded.dst.kind == OperandKind::ExecLo && decoded.src0.reg == 0 && + decoded.src1.kind == OperandKind::Vgpr && decoded.src1.reg == 1, + "compact/VOP3 V_CMPX_CLASS_F16 did not decode to an EXEC compare"); + } + for (const uint32_t modifier : {0x86370000u, 0x87360000u}) { + const uint32_t invalid[] = {0x7d3f70f9u, modifier}; + Instruction decoded; + DecodeInstruction(invalid, 0, decoded); + Check(decoded.opcode == Opcode::UNSUPPORTED && decoded.word_count == 2, + "V_CMPX_CLASS_F16 accepted reserved SDWA selector 7"); + } +} + void TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16() { using namespace ShaderRecompiler; @@ -14830,6 +14870,7 @@ int main() { TestSopkCompareImmediateExtension(); TestNativeStoreCompletion(); TestNewShaderRecompilerCapturedVopcSdwaCmpxClass(); + TestVopcCmpxClassF16Decoder(); TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16(); TestNewShaderRecompilerIrLookupMissFailsExplicitly(); TestNewShaderRecompilerRejectsDppOn64BitCompares();