Share median classifications and preserve RDNA2 signed-zero selection

This commit is contained in:
nmzik
2026-10-19 00:59:31 +02:00
parent 75d7a4311d
commit defb2f11d3
3 changed files with 88 additions and 40 deletions
@@ -160,19 +160,6 @@ uint32_t EmitFPMad32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
return sum;
}
uint32_t EmitFPMedTri32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
const auto min_ab = EmitMinMaxF32Value(state, a, b, false);
const auto min3 = EmitMinMaxF32Value(state, min_ab, c, false);
const auto max_ab = EmitMinMaxF32Value(state, a, b, true);
const auto high_min = EmitMinMaxF32Value(state, max_ab, c, false);
const auto median = EmitMinMaxF32Value(state, min_ab, high_min, true);
const auto nan_ab = Binary(state, spv::OpLogicalOr, TypeBool(state),
EmitClassifyF32(state, a).nan, EmitClassifyF32(state, b).nan);
const auto any_nan =
Binary(state, spv::OpLogicalOr, TypeBool(state), nan_ab, EmitClassifyF32(state, c).nan);
return Select(state, TypeF32(state), any_nan, min3, median);
}
uint32_t EmitFindUMsb64(EmitterState& state, uint32_t value) {
// GLSL FindUMsb is restricted to 32-bit components.
const auto unpacked = Unary(state, spv::OpBitcast, TypeU32Vector(state, 2), value);
@@ -100,10 +100,9 @@ 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);
static uint32_t EmitMinMaxF32(EmitterState& state, uint32_t lhs, uint32_t rhs,
const F32Class& lhs_class, const F32Class& rhs_class,
bool max_value) {
const auto numeric_cond = state.builder.AllocateId();
state.builder.AddFunction(max_value ? spv::OpFOrdGreaterThanEqual : spv::OpFOrdLessThan,
TypeBool(state), numeric_cond, lhs, rhs);
@@ -121,6 +120,34 @@ uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, boo
return EmitBitcastU32ToF32(state, result_bits);
}
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);
return EmitMinMaxF32(state, lhs, rhs, lhs_class, rhs_class, max_value);
}
uint32_t EmitFPMedTri32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
const auto a_class = EmitClassifyF32(state, a);
const auto b_class = EmitClassifyF32(state, b);
const auto c_class = EmitClassifyF32(state, c);
const auto min_ab = EmitMinMaxF32(state, a, b, a_class, b_class, false);
const auto min_ab_class = EmitClassifyF32(state, min_ab);
const auto min3 = EmitMinMaxF32(state, min_ab, c, min_ab_class, c_class, false);
const auto max_ab = EmitMinMaxF32(state, a, b, a_class, b_class, true);
const auto max_ab_class = EmitClassifyF32(state, max_ab);
const auto max3 = EmitMinMaxF32(state, max_ab, c, max_ab_class, c_class, true);
const auto max_bc = EmitMinMaxF32(state, b, c, b_class, c_class, true);
const auto max_ac = EmitMinMaxF32(state, a, c, a_class, c_class, true);
// RDNA2 compares numerically against MAX3, including ties between signed zeros.
const auto a_is_max = Binary(state, spv::OpFOrdEqual, TypeBool(state), max3, a);
const auto b_is_max = Binary(state, spv::OpFOrdEqual, TypeBool(state), max3, b);
const auto median = Select(state, TypeF32(state), a_is_max, max_bc,
Select(state, TypeF32(state), b_is_max, max_ac, max_ab));
const auto nan_ab = EmitLogicalOrBool(state, a_class.nan, b_class.nan);
const auto any_nan = EmitLogicalOrBool(state, nan_ab, c_class.nan);
return Select(state, TypeF32(state), any_nan, min3, median);
}
uint32_t EmitFlushF32DenormToSignedZero(EmitterState& state, uint32_t value) {
const auto bits = state.builder.AllocateId();
const auto abs_bits = state.builder.AllocateId();
+57 -23
View File
@@ -27720,29 +27720,60 @@ TestCase VectorMinMaxF32NanAndSignedZeroEdges() {
O::V_MAX3_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase VectorMed3F32NanUsesMin3Path() {
TestCase VectorMed3F32NanUsesMin3Path(u32 wave) {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovLiteral(&code, 0, 0x40000000u);
AppendVMovLiteral(&code, 1, 0x40400000u);
AppendVMovLiteral(&code, 2, 0x7fa00001u);
AppendVMovLiteral(&code, 3, 0x7fc00000u);
AppendVop3(&code, 0x157, 10, Vgpr(0), Vgpr(1), Vgpr(2));
AppendVop3(&code, 0x157, 11, Vgpr(0), Vgpr(1), Vgpr(3));
AppendVop3(&code, 0x157, 12, Vgpr(2), Vgpr(0), Vgpr(1));
AppendStoreVgpr(&code, 10, 0);
AppendStoreVgpr(&code, 11, 1);
AppendStoreVgpr(&code, 12, 2);
// Runtime inputs keep every MED3 in the emitted module; compare raw result bits.
constexpr u32 cases[][4] = {
{0x40000000u, 0x40400000u, 0x7fa00001u, 0x40000000u},
{0x40000000u, 0x40400000u, 0x7fc00000u, 0x40000000u},
{0x7fa00001u, 0x40000000u, 0x40400000u, 0x40000000u},
{0x40400000u, 0x7fc01234u, 0x40000000u, 0x40000000u},
{0x80000000u, 0x00000000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x80000000u, 0xbf800000u, 0x80000000u},
{0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u},
{0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u},
{0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u},
{0xbf800000u, 0x80000000u, 0x00000000u, 0x00000000u},
{0xbf800000u, 0x00000000u, 0x80000000u, 0x80000000u},
{0x80000000u, 0xbf800000u, 0x00000000u, 0x00000000u},
{0x00000000u, 0xbf800000u, 0x80000000u, 0x80000000u},
{0x80000000u, 0x00000000u, 0xbf800000u, 0x00000000u},
{0x7fc01234u, 0x80000000u, 0x00000000u, 0x80000000u},
{0x3f800000u, 0x40400000u, 0x40000000u, 0x40000000u},
{0xc0400000u, 0xbf800000u, 0xc0000000u, 0xc0000000u},
{0xff800000u, 0x7f800000u, 0x3f800000u, 0x3f800000u},
{0x00000001u, 0x7f800000u, 0x7fc01234u, 0x00000001u},
{0x80000001u, 0x80000001u, 0x00000000u, 0x80000001u},
{0x7fc01234u, 0x7fa00001u, 0xffc05678u, 0xffc05678u},
{0x7fa00001u, 0xffc05678u, 0x40000000u, 0x40000000u}};
TestCase test;
test.name = "VectorMed3F32NanUsesMin3Path";
test.has_compute_info = true;
test.compute_info.wave_size = wave;
test.compute_info.float_mode = 0xf0; // Preserve FP32 input/output denormals.
test.compute_info.threads_num[0] = test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1u;
for (const auto &values : cases)
test.initial.insert(test.initial.end(), values, values + 3);
test.expected = test.initial;
auto &code = test.code;
for (u32 index = 0; index < std::size(cases); ++index) {
for (u32 operand = 0; operand < 3; ++operand) {
AppendVMovU32(&code, 30, (index * 3u + operand) * 4u);
AppendBufferLoadDword(&code, operand, 30);
}
AppendVop3(&code, 0x157, 10, Vgpr(0), Vgpr(1), Vgpr(2));
AppendStoreVgpr(&code, 10, static_cast<u32>(test.expected.size()));
test.expected.push_back(cases[index][3]);
}
AppendEnd(&code);
return {"VectorMed3F32NanUsesMin3Path",
code,
{},
{0x40000000u, 0x40000000u, 0x40000000u},
{O::V_MOV_B32, O::V_MED3_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
test.initial.resize(test.expected.size());
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MED3_F32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.ir_counts = {{"FPMedTri32", std::size(cases)}};
// Measured 17,593/36,607 words; keep classification growth below a single MED3.
test.max_spirv_words = wave == 32u ? 17600u : 36620u;
return test;
}
TestCase VectorFloatConversionOps() {
@@ -42807,7 +42838,8 @@ std::vector<TestCase> MakeCases() {
AddCase(VectorFloatControlContractPreservesInfNan);
AddCase(VectorFloatArithmeticOps);
AddCase(VectorMinMaxF32NanAndSignedZeroEdges);
AddCase(VectorMed3F32NanUsesMin3Path);
cases.push_back(VectorMed3F32NanUsesMin3Path(32));
cases.push_back(VectorMed3F32NanUsesMin3Path(64));
AddCase(VectorFloatConversionOps);
AddCase(VectorFrexpF32Edges);
AddCase(CvtF32ToIntSaturatesNaNAndOutOfRange);
@@ -48691,8 +48723,10 @@ int main(int argc, char **argv) {
RunCase(&vulkan, VectorCompareF32DenormalModes(mode));
}
RunCase(&vulkan, VectorCompareClassF32());
for (const u32 wave : {32u, 64u})
for (const u32 wave : {32u, 64u}) {
RunCase(&vulkan, VectorCompareClassF32ImmediateAndDynamicMasks(wave));
RunCase(&vulkan, VectorMed3F32NanUsesMin3Path(wave));
}
RunCase(&vulkan, VectorCompareF16Ops());
return 0;
}