Compare commits

..

4 Commits

Author SHA1 Message Date
nmzik af3011cd69 shader: add missing 64-bit integer image atomics
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-10-05 04:12:06 +04:00
nmzik de9c15fa79 shader: encode SNORM and scaled formatted buffer stores
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-10-05 04:11:48 +04:00
nmzik e85279ea7b shader: correct DS float min/max operands and special values
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-10-05 04:11:48 +04:00
nmzik d8be245c22 shader: separate legacy FP32 MAD rounding from fused FMA
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-10-05 04:11:48 +04:00
17 changed files with 621 additions and 178 deletions
@@ -223,6 +223,25 @@ uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
}
} // namespace
uint32_t EmitFPFma32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
const auto result = EmitGlsl<GLSLstd450Fma, IR::Type::F32>(state, a, b, c);
state.builder.AddAnnotation(spv::OpDecorate, result, spv::DecorationNoContraction);
return result;
}
uint32_t EmitFPMad32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
// Legacy MAD/MAC round and flush between multiply and add, irrespective of SP_DENORM.
a = EmitFlushF32DenormToSignedZero(state, a);
b = EmitFlushF32DenormToSignedZero(state, b);
c = EmitFlushF32DenormToSignedZero(state, c);
const auto product = EmitFPMul32(state, a, b);
state.builder.AddAnnotation(spv::OpDecorate, product, spv::DecorationNoContraction);
const auto sum = EmitFPAdd32(state, EmitFlushF32DenormToSignedZero(state, product), c);
state.builder.AddAnnotation(spv::OpDecorate, sum, spv::DecorationNoContraction);
return EmitFlushF32DenormToSignedZero(state, 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);
@@ -160,8 +160,8 @@ EMIT_NATIVE(FPMul64, OpFMul, F64, uint32_t, uint32_t)
inline constexpr auto EmitFPFma64 =
EmitGlsl<GLSLstd450Fma, IR::Type::F64, uint32_t, uint32_t, uint32_t>;
uint32_t EmitFPRecip64(EmitterState& state, uint32_t arg0);
inline constexpr auto EmitFPFma32 =
EmitGlsl<GLSLstd450Fma, IR::Type::F32, uint32_t, uint32_t, uint32_t>;
uint32_t EmitFPFma32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2);
uint32_t EmitFPMad32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2);
uint32_t EmitFPMin32(EmitterState& state, uint32_t arg0, uint32_t arg1);
uint32_t EmitFPMax32(EmitterState& state, uint32_t arg0, uint32_t arg1);
uint32_t EmitFPMin64(EmitterState& state, uint32_t arg0, uint32_t arg1);
@@ -321,15 +321,21 @@ inline constexpr auto EmitImageSampleRaw = EmitImage;
inline constexpr auto EmitImageGatherRaw = EmitImage;
inline constexpr auto EmitImageAtomicCompareSwap32 = EmitImage;
inline constexpr auto EmitImageAtomicSwap32 = EmitImage;
inline constexpr auto EmitImageAtomicSwap64 = EmitImage;
inline constexpr auto EmitImageAtomicIAdd32 = EmitImage;
inline constexpr auto EmitImageAtomicIAdd64 = EmitImage;
inline constexpr auto EmitImageAtomicSMin32 = EmitImage;
inline constexpr auto EmitImageAtomicUMin32 = EmitImage;
inline constexpr auto EmitImageAtomicUMin64 = EmitImage;
inline constexpr auto EmitImageAtomicSMax32 = EmitImage;
inline constexpr auto EmitImageAtomicUMax32 = EmitImage;
inline constexpr auto EmitImageAtomicUMax64 = EmitImage;
inline constexpr auto EmitImageAtomicAnd32 = EmitImage;
inline constexpr auto EmitImageAtomicAnd64 = EmitImage;
inline constexpr auto EmitImageAtomicOr32 = EmitImage;
inline constexpr auto EmitImageAtomicOr64 = EmitImage;
inline constexpr auto EmitImageAtomicXor32 = EmitImage;
inline constexpr auto EmitImageAtomicXor64 = EmitImage;
inline constexpr auto EmitImageAtomicFMin32 = EmitImage;
inline constexpr auto EmitImageAtomicFMax32 = EmitImage;
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -463,6 +463,9 @@ void EmitAtomicMemoryBarrier(EmitterState& state, IR::ResourceKind kind);
uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
bool max_value);
uint32_t EmitDsFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
bool max_value);
uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t control);
inline constexpr auto EmitSelectValueU32 =
@@ -710,24 +710,37 @@ uint32_t FormattedLoad(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
uint32_t EncodeFormattedStoreComponent(ValueEmitContext& ctx,
const Format::BufferFormatInfo& info,
uint32_t component, uint32_t data) {
auto& state = ctx.state;
const auto bits = info.component_bits[component];
if (info.type == Format::ComponentType::Unorm) {
const auto value = EmitBitCastF32U32(ctx.state, data);
const auto clamped = EmitFPMin32(
ctx.state, EmitFPMax32(ctx.state, value, ConstantF32Value(ctx.state, 0.0f)),
ConstantF32Value(ctx.state, 1.0f));
const auto scaled = EmitFPMul32(
ctx.state, clamped, ConstantF32Value(ctx.state, float((1u << bits) - 1u)));
return Unary(ctx.state, spv::OpConvertFToU, TypeU32(ctx.state),
EmitFPRoundEven32(ctx.state, scaled));
const bool normalized = info.type == Format::ComponentType::Unorm ||
info.type == Format::ComponentType::Snorm;
const bool scaled = info.type == Format::ComponentType::Uscaled ||
info.type == Format::ComponentType::Sscaled;
if (normalized || scaled) {
// Two-bit packed channels are unsigned even in signed formats.
const bool is_signed = bits != 2u && (info.type == Format::ComponentType::Snorm ||
info.type == Format::ComponentType::Sscaled);
const auto max_value = static_cast<float>((1u << (bits - (is_signed ? 1u : 0u))) - 1u);
const auto lower = normalized ? (is_signed ? -1.0f : 0.0f)
: (is_signed ? -max_value - 1.0f : 0.0f);
const auto upper = normalized ? 1.0f : max_value;
auto value = Select(state, TypeF32(state), EmitClassifyF32Bits(state, data).nan,
ConstantF32Value(state, 0.0f), EmitBitCastF32U32(state, data));
value = EmitGlsl<GLSLstd450FClamp, IR::Type::F32>(
state, value, ConstantF32Value(state, lower), ConstantF32Value(state, upper));
if (normalized) {
value = EmitFPRoundEven32(
state, EmitFPMul32(state, value, ConstantF32Value(state, max_value)));
}
// Float-to-integer conversion truncates scaled values toward zero.
value = Unary(state, is_signed ? spv::OpConvertFToS : spv::OpConvertFToU,
is_signed ? TypeI32(state) : TypeU32(state), value);
return is_signed ? Unary(state, spv::OpBitcast, TypeU32(state), value) : value;
}
if (bits == 16u && (info.type == Format::ComponentType::Snorm ||
info.type == Format::ComponentType::Float)) {
const auto value = EmitBitCastF32U32(ctx.state, data);
const auto pair = EmitCompositeConstructF32x2(ctx.state, value,
ConstantF32Value(ctx.state, 0.0f));
return info.type == Format::ComponentType::Float ? EmitPackHalf2x16(ctx.state, pair)
: EmitPackSnorm2x16(ctx.state, pair);
if (bits == 16u && info.type == Format::ComponentType::Float) {
const auto pair = EmitCompositeConstructF32x2(
state, EmitBitCastF32U32(state, data), ConstantF32Value(state, 0.0f));
return EmitPackHalf2x16(state, pair);
}
return data;
}
@@ -1227,34 +1240,10 @@ uint32_t EmitBufferFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst) {
void EmitSharedFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto& mem = ctx.Memory(inst);
const bool max_value = inst.GetOpcode() == IR::ValueOpcode::SharedAtomicFMax32;
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto index = DwordIndex(ctx, inst, mem);
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
EmitIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
ctx.state.builder.AddFunction(spv::OpStore, ctx.scratch_u32_variable,
ctx.Arg(inst, 1));
const auto data = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), data,
ctx.scratch_u32_variable);
AtomicUpdate(
ctx.state, EmitMemoryElementPointer(ctx.state, resource, index),
mem.kind, [&](uint32_t old) {
const auto old_f =
Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), old);
const auto compare_f =
Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), ctx.Arg(inst, 2));
const auto data_f =
Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), data);
const auto compare = Binary(
ctx.state, max_value ? spv::OpFOrdGreaterThan : spv::OpFOrdLessThan,
TypeBool(ctx.state), max_value ? old_f : compare_f,
max_value ? compare_f : old_f);
return Unary(ctx.state, spv::OpBitcast, TypeU32(ctx.state),
Select(ctx.state, TypeF32(ctx.state), compare, data_f, old_f));
});
});
});
EmitAtomicUpdate(ctx, inst, mem,
[max_value](EmitterState& state, uint32_t old, uint32_t value) {
return EmitDsFloatAtomicReplacement(state, old, value, max_value);
});
}
uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
@@ -361,10 +361,12 @@ spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
case IR::ValueOpcode::ImageAtomicCompareSwap32:
case IR::ValueOpcode::BufferAtomicCmpSwap32: return spv::OpAtomicCompareExchange;
case IR::ValueOpcode::ImageAtomicSwap32:
case IR::ValueOpcode::ImageAtomicSwap64:
case IR::ValueOpcode::BufferAtomicSwap32:
case IR::ValueOpcode::BufferAtomicSwap64:
case IR::ValueOpcode::SharedAtomicSwap32: return spv::OpAtomicExchange;
case IR::ValueOpcode::ImageAtomicIAdd32:
case IR::ValueOpcode::ImageAtomicIAdd64:
case IR::ValueOpcode::BufferAtomicIAdd32:
case IR::ValueOpcode::SharedAtomicIAdd64:
case IR::ValueOpcode::SharedAtomicIAdd32: return spv::OpAtomicIAdd;
@@ -374,6 +376,7 @@ spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
case IR::ValueOpcode::BufferAtomicSMin32:
case IR::ValueOpcode::SharedAtomicSMin32: return spv::OpAtomicSMin;
case IR::ValueOpcode::ImageAtomicUMin32:
case IR::ValueOpcode::ImageAtomicUMin64:
case IR::ValueOpcode::BufferAtomicUMin32:
case IR::ValueOpcode::SharedAtomicUMin32: return spv::OpAtomicUMin;
case IR::ValueOpcode::ImageAtomicSMax32:
@@ -384,15 +387,18 @@ spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
case IR::ValueOpcode::BufferAtomicUMax32:
case IR::ValueOpcode::SharedAtomicUMax32: return spv::OpAtomicUMax;
case IR::ValueOpcode::ImageAtomicAnd32:
case IR::ValueOpcode::ImageAtomicAnd64:
case IR::ValueOpcode::BufferAtomicAnd32:
case IR::ValueOpcode::BufferAtomicAnd64:
case IR::ValueOpcode::SharedAtomicAnd32: return spv::OpAtomicAnd;
case IR::ValueOpcode::ImageAtomicOr32:
case IR::ValueOpcode::ImageAtomicOr64:
case IR::ValueOpcode::BufferAtomicOr32:
case IR::ValueOpcode::BufferAtomicOr64:
case IR::ValueOpcode::SharedAtomicOr64:
case IR::ValueOpcode::SharedAtomicOr32: return spv::OpAtomicOr;
case IR::ValueOpcode::ImageAtomicXor32:
case IR::ValueOpcode::ImageAtomicXor64:
case IR::ValueOpcode::BufferAtomicXor32:
case IR::ValueOpcode::SharedAtomicXor32: return spv::OpAtomicXor;
default: return spv::OpNop;
@@ -438,6 +444,20 @@ void EmitAtomicMemoryBarrier(EmitterState& state, IR::ResourceKind kind) {
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | memory));
}
namespace {
uint32_t FloatOrderKey(EmitterState& state, uint32_t bits) {
const auto negative = EmitCompareU32Constant(state, spv::OpINotEqual,
EmitAndConstant(state, bits, 0x80000000u), 0u);
const auto negative_key = state.builder.AllocateId();
state.builder.AddFunction(spv::OpNot, TypeU32(state), negative_key, bits);
const auto positive_key =
EmitBinaryU32(state, spv::OpBitwiseXor, bits, ConstantU32(state, 0x80000000u));
return EmitSelectValueU32(state, negative, negative_key, positive_key);
}
} // namespace
uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
bool max_value) {
struct OrderedBits {
@@ -447,15 +467,7 @@ uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t
};
const auto classify = [&](uint32_t bits) {
const auto cls = EmitClassifyF32Bits(state, bits);
const auto negative = EmitCompareU32Constant(state, spv::OpINotEqual,
EmitAndConstant(state, bits, 0x80000000u), 0u);
const auto negative_key = state.builder.AllocateId();
state.builder.AddFunction(spv::OpNot, TypeU32(state), negative_key, bits);
const auto positive_key =
EmitBinaryU32(state, spv::OpBitwiseXor, bits, ConstantU32(state, 0x80000000u));
return OrderedBits {
cls.nan, cls.zero,
EmitSelectValueU32(state, negative, negative_key, positive_key)};
return OrderedBits {cls.nan, cls.zero, FloatOrderKey(state, bits)};
};
const auto source_class = classify(source);
const auto old_class = classify(old);
@@ -470,6 +482,45 @@ uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t
old);
}
uint32_t EmitDsFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
bool max_value) {
struct OrderedBits {
uint32_t nan;
uint32_t signaling_nan;
uint32_t key;
};
const auto classify = [&](uint32_t bits) {
const auto abs = EmitAndConstant(state, bits, 0x7fffffffu);
const auto nan = EmitCompareU32Constant(state, spv::OpUGreaterThan, abs, 0x7f800000u);
const auto signaling_nan = EmitLogicalAndBool(
state, nan, EmitCompareU32Constant(state, spv::OpIEqual,
EmitAndConstant(state, bits, 0x00400000u), 0u));
const auto small = EmitCompareU32Constant(state, spv::OpULessThan, abs, 0x00800000u);
const auto sign = EmitAndConstant(state, bits, 0x80000000u);
const auto positive_denorm = EmitLogicalAndBool(
state, EmitCompareU32Constant(state, spv::OpIEqual, sign, 0u),
EmitCompareU32Constant(state, spv::OpINotEqual, abs, 0u));
// Negative denorms tie with -0; positive denorms tie only with each other.
const auto small_bits = EmitSelectValueU32(
state, positive_denorm, ConstantU32(state, 1u), sign);
const auto key_bits = EmitSelectValueU32(state, small, small_bits, bits);
return OrderedBits {nan, signaling_nan, FloatOrderKey(state, key_bits)};
};
const auto lhs = classify(old);
const auto rhs = classify(source);
const auto choose_source = state.builder.AllocateId();
state.builder.AddFunction(max_value ? spv::OpUGreaterThan : spv::OpULessThanEqual,
TypeBool(state), choose_source, rhs.key, lhs.key);
// Keep original bits: ordering denorms does not flush the selected operand.
auto result = EmitSelectValueU32(state, choose_source, source, old);
result = EmitSelectValueU32(state, lhs.nan, source, result);
result = EmitSelectValueU32(state, rhs.nan, old, result);
result = EmitSelectValueU32(state, rhs.signaling_nan,
EmitOrU32(state, source, ConstantU32(state, 0x00400000u)), result);
return EmitSelectValueU32(state, lhs.signaling_nan,
EmitOrU32(state, old, ConstantU32(state, 0x00400000u)), result);
}
uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t control) {
if ((control & 0xc000u) == 0xc000u) {
const uint32_t mask = control & 0x1fu;
@@ -689,9 +689,7 @@ void EmitProgram(EmitterState& state) {
DefineBvhIntersect(state);
for (const auto* block: program.blocks) {
if (std::ranges::any_of(*block, [](const IR::Inst& inst) {
return inst.GetOpcode() == IR::ValueOpcode::SwizzleU32 ||
inst.GetOpcode() == IR::ValueOpcode::SharedAtomicFMin32 ||
inst.GetOpcode() == IR::ValueOpcode::SharedAtomicFMax32;
return inst.GetOpcode() == IR::ValueOpcode::SwizzleU32;
})) {
ctx.scratch_u32_variable = state.builder.AllocateId();
if (state.lane_count == 2) {
@@ -20,6 +20,12 @@ struct MimgGatherInfo {
uint32_t flags = 0;
};
struct MimgAtomicInfo {
uint32_t encoding = 0;
Opcode decoded = Opcode::UNSUPPORTED;
bool supports_64 = false;
};
constexpr ImageDimension DecodeImageDimension(uint32_t dim) {
switch (dim) {
case 0u: return ImageDimension::Dim1D;
@@ -190,17 +196,17 @@ constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = {
{0x61u, Opcode::IMAGE_GATHER4H, ImageSampleFlagGatherHorizontal},
};
constexpr Detail::OpcodeMap MIMG_ATOMIC_OPCODE_LIST[] = {
{0x0fu, Opcode::IMAGE_ATOMIC_SWAP},
constexpr MimgAtomicInfo MIMG_ATOMIC_OPCODE_LIST[] = {
{0x0fu, Opcode::IMAGE_ATOMIC_SWAP, true},
{0x10u, Opcode::IMAGE_ATOMIC_CMPSWAP},
{0x11u, Opcode::IMAGE_ATOMIC_ADD},
{0x11u, Opcode::IMAGE_ATOMIC_ADD, true},
{0x14u, Opcode::IMAGE_ATOMIC_SMIN},
{0x15u, Opcode::IMAGE_ATOMIC_UMIN},
{0x15u, Opcode::IMAGE_ATOMIC_UMIN, true},
{0x16u, Opcode::IMAGE_ATOMIC_SMAX},
{0x17u, Opcode::IMAGE_ATOMIC_UMAX},
{0x18u, Opcode::IMAGE_ATOMIC_AND},
{0x19u, Opcode::IMAGE_ATOMIC_OR},
{0x1au, Opcode::IMAGE_ATOMIC_XOR},
{0x17u, Opcode::IMAGE_ATOMIC_UMAX, true},
{0x18u, Opcode::IMAGE_ATOMIC_AND, true},
{0x19u, Opcode::IMAGE_ATOMIC_OR, true},
{0x1au, Opcode::IMAGE_ATOMIC_XOR, true},
{0x1eu, Opcode::IMAGE_ATOMIC_FMIN},
{0x1fu, Opcode::IMAGE_ATOMIC_FMAX},
};
@@ -210,7 +216,7 @@ constexpr auto MIMG_GATHER_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_GATHER_OPCO
constexpr auto MIMG_ATOMIC_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_ATOMIC_OPCODE_LIST);
Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const MimgGatherInfo* gather,
const Detail::OpcodeMap* atomic) {
const MimgAtomicInfo* atomic) {
if (sample != nullptr) {
return Opcode::IMAGE_SAMPLE;
}
@@ -246,7 +252,7 @@ uint32_t DecodeMimgSampleFlags(const MimgSampleInfo* sample, const MimgGatherInf
uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension,
const MimgSampleInfo* sample, const MimgGatherInfo* gather,
const Detail::OpcodeMap* atomic) {
const MimgAtomicInfo* atomic) {
if (sample != nullptr) {
return ImageSampleAddressComponents(sample->flags, dimension);
}
@@ -370,7 +376,7 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
SetUnsupported(inst, Family::MIMG, opcode, "MIMG image atomic has invalid DMASK");
} else if (inst.dmask == mask64) {
inst.data_bits = 64u;
if (inst.opcode != Opcode::IMAGE_ATOMIC_UMAX) {
if (!atomic->supports_64) {
SetUnsupported(inst, Family::MIMG, opcode,
"MIMG 64-bit image atomic opcode is not implemented");
}
@@ -205,9 +205,9 @@ uint32_t DsSourceCount(Opcode opcode) {
case Opcode::DS_WRITE2ST64_B32:
case Opcode::DS_WRITE2_B64:
case Opcode::DS_WRITE2ST64_B64:
case Opcode::DS_MSKOR_B32:
case Opcode::DS_MSKOR_B32: return 3u;
case Opcode::DS_MIN_F32:
case Opcode::DS_MAX_F32: return 3u;
case Opcode::DS_MAX_F32:
case Opcode::DS_PERMUTE_B32:
case Opcode::DS_BPERMUTE_B32: return 2u;
case Opcode::DS_READ_ADDTID_B32:
@@ -224,6 +224,9 @@ enum class Opcode {
V_MAC_F32,
V_MADMK_F32,
V_MADAK_F32,
V_FMAC_F32,
V_FMAMK_F32,
V_FMAAK_F32,
V_MIN_F32,
V_MAX_F32,
V_ADD_F16,
@@ -65,9 +65,9 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
{0x2au, Opcode::V_SUBREV_CO_CI_U32},
{0x26u, Opcode::V_SUB_NC_U32, Vop2SdwaProfile::IntegerPartialDestination},
{0x27u, Opcode::V_SUBREV_NC_U32, Vop2SdwaProfile::IntegerFullDestination},
{0x2bu, Opcode::V_MAC_F32},
{0x2cu, Opcode::V_MADMK_F32},
{0x2du, Opcode::V_MADAK_F32},
{0x2bu, Opcode::V_FMAC_F32},
{0x2cu, Opcode::V_FMAMK_F32},
{0x2du, Opcode::V_FMAAK_F32},
{0x2fu, Opcode::V_CVT_PKRTZ_F16_F32, Vop2SdwaProfile::PackedFloat16},
{0x32u, Opcode::V_ADD_F16, Vop2SdwaProfile::Float16},
{0x33u, Opcode::V_SUB_F16, Vop2SdwaProfile::Float16},
@@ -800,6 +800,9 @@ bool IsVop2FloatOpcode(Opcode opcode) {
case Opcode::V_MAC_F32:
case Opcode::V_MADMK_F32:
case Opcode::V_MADAK_F32:
case Opcode::V_FMAC_F32:
case Opcode::V_FMAMK_F32:
case Opcode::V_FMAAK_F32:
case Opcode::V_CVT_PKRTZ_F16_F32:
case Opcode::V_ADD_F16:
case Opcode::V_SUB_F16:
@@ -1057,6 +1060,7 @@ void FinalizeVop2Instruction(std::span<const uint32_t> code, uint32_t word_index
Instruction& inst) {
switch (inst.opcode) {
case Opcode::V_MADMK_F32:
case Opcode::V_FMAMK_F32:
case Opcode::V_FMAMK_F16:
inst.src2 = inst.src1;
inst.src1 = {};
@@ -1064,6 +1068,7 @@ void FinalizeVop2Instruction(std::span<const uint32_t> code, uint32_t word_index
inst.src_count = 3;
break;
case Opcode::V_MADAK_F32:
case Opcode::V_FMAAK_F32:
case Opcode::V_FMAAK_F16:
inst.src2 = {};
inst.src2.kind = OperandKind::LiteralConstant;
@@ -1401,6 +1406,7 @@ bool SupportsNativeVop3SourceModifiers(Opcode opcode) {
case Opcode::V_MIN_F32:
case Opcode::V_MAX_F32:
case Opcode::V_MAC_F32:
case Opcode::V_FMAC_F32:
case Opcode::V_MAD_F32:
case Opcode::V_ADD_F64:
case Opcode::V_MUL_F64:
@@ -1436,6 +1442,7 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) {
case Opcode::V_MIN_F32:
case Opcode::V_MAX_F32:
case Opcode::V_MAC_F32:
case Opcode::V_FMAC_F32:
case Opcode::V_MAD_F32:
case Opcode::V_FMA_F32:
case Opcode::V_FMA_F16:
@@ -546,7 +546,10 @@ void Translator::BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode
}
}
void Translator::IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
void Translator::IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode32,
IR::ValueOpcode opcode64) {
const auto opcode = inst.data_bits == 64u ? opcode64 : opcode32;
EXIT_IF(opcode == IR::ValueOpcode::Count);
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource = GetImageResource(memory);
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 1));
@@ -1041,24 +1044,30 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::IMAGE_ATOMIC_CMPSWAP:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicCompareSwap32);
case Decoder::Opcode::IMAGE_ATOMIC_SWAP:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicSwap32);
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicSwap32,
IR::ValueOpcode::ImageAtomicSwap64);
case Decoder::Opcode::IMAGE_ATOMIC_ADD:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicIAdd32);
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicIAdd32,
IR::ValueOpcode::ImageAtomicIAdd64);
case Decoder::Opcode::IMAGE_ATOMIC_SMIN:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicSMin32);
case Decoder::Opcode::IMAGE_ATOMIC_UMIN:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicUMin32);
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicUMin32,
IR::ValueOpcode::ImageAtomicUMin64);
case Decoder::Opcode::IMAGE_ATOMIC_SMAX:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicSMax32);
case Decoder::Opcode::IMAGE_ATOMIC_UMAX:
return IMAGE_ATOMIC(inst, inst.data_bits == 64u ? IR::ValueOpcode::ImageAtomicUMax64
: IR::ValueOpcode::ImageAtomicUMax32);
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicUMax32,
IR::ValueOpcode::ImageAtomicUMax64);
case Decoder::Opcode::IMAGE_ATOMIC_AND:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicAnd32);
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicAnd32,
IR::ValueOpcode::ImageAtomicAnd64);
case Decoder::Opcode::IMAGE_ATOMIC_OR:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicOr32);
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicOr32,
IR::ValueOpcode::ImageAtomicOr64);
case Decoder::Opcode::IMAGE_ATOMIC_XOR:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicXor32);
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicXor32,
IR::ValueOpcode::ImageAtomicXor64);
case Decoder::Opcode::IMAGE_ATOMIC_FMIN:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicFMin32);
case Decoder::Opcode::IMAGE_ATOMIC_FMAX:
@@ -86,7 +86,8 @@ private:
void BUFFER_LOAD(const Decoder::Instruction& inst);
void BUFFER_STORE(const Decoder::Instruction& inst);
void BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
void IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
void IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode32,
IR::ValueOpcode opcode64 = IR::ValueOpcode::Count);
void DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool returns_value);
void FLAT_LOAD(const Decoder::Instruction& inst);
void FLAT_STORE(const Decoder::Instruction& inst);
@@ -479,10 +479,13 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_MIN_F32: return FloatBinary(inst, IR::ValueOpcode::FPMin32, false);
case O::V_MAX_F32: return FloatBinary(inst, IR::ValueOpcode::FPMax32, false);
case O::V_LDEXP_F32: return FloatBinary(inst, IR::ValueOpcode::FPLdexp, false);
case O::V_MAC_F32: return FloatTernary(inst, IR::ValueOpcode::FPFma32, true, true);
case O::V_MAC_F32: return FloatTernary(inst, IR::ValueOpcode::FPMad32, true, true);
case O::V_MADMK_F32:
case O::V_MADAK_F32:
case O::V_MAD_F32:
case O::V_MAD_F32: return FloatTernary(inst, IR::ValueOpcode::FPMad32, false, true);
case O::V_FMAC_F32: return FloatTernary(inst, IR::ValueOpcode::FPFma32, true, true);
case O::V_FMAMK_F32:
case O::V_FMAAK_F32:
case O::V_FMA_F32: return FloatTernary(inst, IR::ValueOpcode::FPFma32, false, true);
case O::V_FMA_F64: return FloatTernary(inst, IR::ValueOpcode::FPFma64, false, false);
case O::V_MIN3_F32: return FloatTernary(inst, IR::ValueOpcode::FPMinTri32, false, false);
@@ -224,15 +224,21 @@ ImageOpcodeInfo ImageOpcodeInfoOf(ValueOpcode opcode) {
return {ImageAccess::Write, ImageResourceClass::Storage, false};
case ValueOpcode::ImageAtomicCompareSwap32:
case ValueOpcode::ImageAtomicSwap32:
case ValueOpcode::ImageAtomicSwap64:
case ValueOpcode::ImageAtomicIAdd32:
case ValueOpcode::ImageAtomicIAdd64:
case ValueOpcode::ImageAtomicSMin32:
case ValueOpcode::ImageAtomicUMin32:
case ValueOpcode::ImageAtomicUMin64:
case ValueOpcode::ImageAtomicSMax32:
case ValueOpcode::ImageAtomicUMax32:
case ValueOpcode::ImageAtomicUMax64:
case ValueOpcode::ImageAtomicAnd32:
case ValueOpcode::ImageAtomicAnd64:
case ValueOpcode::ImageAtomicOr32:
case ValueOpcode::ImageAtomicOr64:
case ValueOpcode::ImageAtomicXor32:
case ValueOpcode::ImageAtomicXor64:
case ValueOpcode::ImageAtomicFMin32:
case ValueOpcode::ImageAtomicFMax32:
return {ImageAccess::Atomic, ImageResourceClass::Storage, false};
@@ -179,6 +179,7 @@ VALUE_OPCODE(FPCmpClass32, U1, F32, U32)
VALUE_OPCODE(FPAdd32, F32, F32, F32)
VALUE_OPCODE(FPSub32, F32, F32, F32)
VALUE_OPCODE(FPFma32, F32, F32, F32, F32)
VALUE_OPCODE(FPMad32, F32, F32, F32, F32)
VALUE_OPCODE(FPMul32, F32, F32, F32)
VALUE_OPCODE(FPAdd64, F64, F64, F64)
VALUE_OPCODE(FPMul64, F64, F64, F64)
@@ -279,8 +280,8 @@ VALUE_OPCODE(WriteSharedU32, Void, U32, U32, U1)
VALUE_OPCODE(WriteSharedU32x2, Void, U32, U32, U32, U1)
VALUE_OPCODE(WriteSharedU32x3, Void, U32, U32, U32, U32, U1)
VALUE_OPCODE(WriteSharedU32x4, Void, U32, U32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicFMin32, Void, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicFMax32, Void, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicFMin32, Void, U32, U32, U1)
VALUE_OPCODE(SharedAtomicFMax32, Void, U32, U32, U1)
VALUE_OPCODE(SharedAtomicSwap32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicIAdd32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicIAdd64, Void, U32, U64, U1)
@@ -309,15 +310,21 @@ VALUE_OPCODE(ImageSampleRaw, U32x4, ImageResource, SamplerResource, ImageAddress
VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress, U32, U32, U32, U32)
VALUE_OPCODE(ImageAtomicCompareSwap32, U32, ImageResource, ImageAddress, U32, U32, U1)
VALUE_OPCODE(ImageAtomicSwap32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicSwap64, U64, ImageResource, ImageAddress, U64, U1)
VALUE_OPCODE(ImageAtomicIAdd32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicIAdd64, U64, ImageResource, ImageAddress, U64, U1)
VALUE_OPCODE(ImageAtomicSMin32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicUMin32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicUMin64, U64, ImageResource, ImageAddress, U64, U1)
VALUE_OPCODE(ImageAtomicSMax32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicUMax32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicUMax64, U64, ImageResource, ImageAddress, U64, U1)
VALUE_OPCODE(ImageAtomicAnd32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicAnd64, U64, ImageResource, ImageAddress, U64, U1)
VALUE_OPCODE(ImageAtomicOr32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicOr64, U64, ImageResource, ImageAddress, U64, U1)
VALUE_OPCODE(ImageAtomicXor32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicXor64, U64, ImageResource, ImageAddress, U64, U1)
VALUE_OPCODE(ImageAtomicFMin32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicFMax32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(GetAttribute, U32, U32, U32)
+390 -41
View File
@@ -18489,6 +18489,9 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode,
case Opcode::V_MAC_F32:
case Opcode::V_MADMK_F32:
case Opcode::V_MADAK_F32:
case Opcode::V_FMAC_F32:
case Opcode::V_FMAMK_F32:
case Opcode::V_FMAAK_F32:
case Opcode::V_MIN_F32:
case Opcode::V_MAX_F32:
case Opcode::V_MAD_F32:
@@ -23508,6 +23511,86 @@ TestCase VectorDppBoundsControlZeroPreservesDestination() {
return test;
}
TestCase VectorMadF32RoundsProduct() {
using O = ShaderOpcode;
std::vector<u32> code;
for (u32 source = 0; source < 3; source++) {
AppendVMovU32(&code, 30, source * sizeof(u32));
AppendBufferLoadDword(&code, source, 30);
}
AppendVMovLiteral(&code, 10, 0xbf800000u);
AppendVMovLiteral(&code, 11, 0xbf800000u);
AppendVMovLiteral(&code, 15, 0xbf800000u);
AppendVMovLiteral(&code, 16, 0xbf800000u);
code.push_back(EncodeVop2(0x1f, 10, Vgpr(0), 1));
AppendVop3(&code, 0x11f, 11, Vgpr(0), Vgpr(1), 0);
AppendVop3(&code, 0x141, 12, Vgpr(0), Vgpr(1), Vgpr(2));
code.push_back(EncodeVop2(0x20, 13, Vgpr(0), 2));
code.push_back(0x3f7ffffeu);
code.push_back(EncodeVop2(0x21, 14, Vgpr(0), 1));
code.push_back(0xbf800000u);
code.push_back(EncodeVop2(0x2b, 15, Vgpr(0), 1));
AppendVop3(&code, 0x12b, 16, Vgpr(0), Vgpr(1), 0);
AppendVop3(&code, 0x14b, 17, Vgpr(0), Vgpr(1), Vgpr(2));
code.push_back(EncodeVop2(0x2c, 18, Vgpr(0), 2));
code.push_back(0x3f7ffffeu);
code.push_back(EncodeVop2(0x2d, 19, Vgpr(0), 1));
code.push_back(0xbf800000u);
for (u32 i = 0; i < 10; i++) {
AppendStoreVgpr(&code, 10 + i, i);
}
AppendEnd(&code);
TestCase test;
test.name = "VectorMadF32RoundsProduct";
test.code = std::move(code);
test.initial = {0x3f800001u, 0x3f7ffffeu, 0xbf800000u};
// (1+2^-23)*(1-2^-23)-1 rounds to zero for MAD, but -2^-46 for FMA.
test.expected = {0u, 0u, 0u, 0u, 0u, 0xa8800000u, 0xa8800000u,
0xa8800000u, 0xa8800000u, 0xa8800000u};
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MAC_F32,
O::V_MAD_F32, O::V_MADMK_F32, O::V_MADAK_F32,
O::V_FMAC_F32, O::V_FMA_F32, O::V_FMAMK_F32, O::V_FMAAK_F32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpFMul", "OpFAdd", "NoContraction", "Fma"};
return test;
}
TestCase VectorMadF32FlushesDenorms() {
using O = ShaderOpcode;
std::vector<u32> code;
for (u32 sample = 0; sample < 5; sample++) {
for (u32 source = 0; source < 3; source++) {
AppendVMovU32(&code, 30, (sample * 3 + source) * sizeof(u32));
AppendBufferLoadDword(&code, source, 30);
}
AppendVop3(&code, 0x141, 10 + sample, Vgpr(0), Vgpr(1), Vgpr(2));
}
for (u32 sample = 0; sample < 5; sample++) {
AppendStoreVgpr(&code, 10 + sample, sample);
}
AppendEnd(&code);
TestCase test;
test.name = "VectorMadF32FlushesDenorms";
test.code = std::move(code);
// Input denorms in each operand, an intermediate denorm, and a negative result denorm.
test.initial = {0x00400000u, 0x40000000u, 0u,
0x40000000u, 0x00400000u, 0u,
0u, 0x3f800000u, 0x00400000u,
0x00800000u, 0x3f000000u, 0x00800000u,
0x00800000u, 0x3f800000u, 0x80800001u};
test.expected = {0u, 0u, 0u, 0x00800000u, 0x80000000u};
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MAD_F32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
// Legacy MAD always flushes, even when the shader requests denorm preservation.
test.compute_info.float_mode = 0xf0;
test.has_compute_info = true;
return test;
}
TestCase Vop3FmacF32NegatedSourceAccumulates() {
using O = ShaderOpcode;
@@ -23531,7 +23614,7 @@ TestCase Vop3FmacF32NegatedSourceAccumulates() {
0x3f800001u, 0x3f7ffffeu, 0x3f800000u};
// 10 - 2*3 = 4; 1 - (1+2^-23)*(1-2^-23) = 2^-46, not rounded zero.
test.expected = {0x40800000u, 0x28800000u};
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MAC_F32,
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_FMAC_F32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpFNegate", "Fma"};
return test;
@@ -28046,6 +28129,105 @@ TestCase BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords() {
return test;
}
std::vector<TestCase> FormattedStoreConversionCases() {
using F = Prospero::BufferFormat;
using O = ShaderOpcode;
struct ConversionCase {
const char *name[2];
F format;
u32 components;
std::array<float, 4> values;
u32 expected;
};
constexpr float nan = std::bit_cast<float>(0x7fc12345u);
constexpr float inf = std::bit_cast<float>(0x7f800000u);
constexpr ConversionCase inputs[] = {
{{"BufferStoreSnorm8Range", "TBufferStoreSnorm8Range"},
F::k8_8_8_8SNorm, 4, {-2.0f, 0.25f, 0.75f, 2.0f}, 0x7f5f2081u},
{{"BufferStoreSnorm8Ties", "TBufferStoreSnorm8Ties"},
F::k8_8SNorm, 2, {0.5f, -0.5f}, 0xffffc040u},
{{"BufferStoreSnormPackedAlpha", "TBufferStoreSnormPackedAlpha"},
F::k10_10_10_2SNorm, 4, {-1.0f, -0.5f, 0.5f, 1.0f}, 0xd00c0201u},
{{"BufferStoreSnormPackedFirstChannel", "TBufferStoreSnormPackedFirstChannel"},
F::k2_10_10_10SNorm, 4, {1.0f, -1.0f, 0.5f, -0.5f}, 0xc0100807u},
{{"BufferStoreSnormPackedZeroFillX", "TBufferStoreSnormPackedZeroFillX"},
F::k10_10_10_2SNorm, 1, {-1.0f}, 0x00000201u},
{{"BufferStoreSnormPackedZeroFillXy", "TBufferStoreSnormPackedZeroFillXy"},
F::k10_10_10_2SNorm, 2, {-1.0f, 1.0f}, 0x0007fe01u},
{{"BufferStoreSnormPackedZeroFillXyz", "TBufferStoreSnormPackedZeroFillXyz"},
F::k10_10_10_2SNorm, 3, {-1.0f, 1.0f, -1.0f}, 0x2017fe01u},
{{"BufferStoreSnorm8ZeroFillX", "TBufferStoreSnorm8ZeroFillX"},
F::k8_8_8_8SNorm, 1, {-1.0f}, 0x00000081u},
{{"BufferStoreSnorm16ZeroFillX", "TBufferStoreSnorm16ZeroFillX"},
F::k16_16SNorm, 1, {-1.0f}, 0x00008001u},
{{"BufferStoreUscaledZeroFillXy", "TBufferStoreUscaledZeroFillXy"},
F::k8_8_8_8UScaled, 2, {42.0f, 99.0f}, 0x0000632au},
{{"BufferStoreSscaledZeroFillXyz", "TBufferStoreSscaledZeroFillXyz"},
F::k8_8_8_8SScaled, 3, {-42.0f, 127.0f, -128.0f}, 0x00807fd6u},
{{"BufferStoreUscaled8Fraction", "TBufferStoreUscaled8Fraction"},
F::k8UScaled, 1, {42.875f}, 0xffffff2au},
{{"BufferStoreSscaled8Fraction", "TBufferStoreSscaled8Fraction"},
F::k8SScaled, 1, {-42.875f}, 0xffffffd6u},
{{"BufferStoreUscaled8Range", "TBufferStoreUscaled8Range"},
F::k8_8_8_8UScaled, 4, {-1.0f, 255.875f, 256.0f, 0.0f}, 0x00ffff00u},
{{"BufferStoreSscaled8Range", "TBufferStoreSscaled8Range"},
F::k8_8_8_8SScaled, 4, {-129.0f, -128.75f, 127.875f, 128.0f}, 0x7f7f8080u},
{{"BufferStoreUscaled16Range", "TBufferStoreUscaled16Range"},
F::k16_16UScaled, 2, {65536.0f, 42.875f}, 0x002affffu},
{{"BufferStoreSscaled16Range", "TBufferStoreSscaled16Range"},
F::k16_16SScaled, 2, {-32769.0f, 32768.0f}, 0x7fff8000u},
{{"BufferStoreUscaledPacked", "TBufferStoreUscaledPacked"},
F::k10_10_10_2UScaled, 4, {1024.0f, 3.875f, -1.0f, 4.0f}, 0xc0000fffu},
{{"BufferStoreSscaledPackedAlpha", "TBufferStoreSscaledPackedAlpha"},
F::k10_10_10_2SScaled, 4, {-512.75f, 511.875f, -3.875f, 3.875f}, 0xffd7fe00u},
{{"BufferStoreSscaledPackedFirstChannel", "TBufferStoreSscaledPackedFirstChannel"},
F::k2_10_10_10SScaled, 4, {3.0f, -512.0f, 511.0f, -1.0f}, 0xffdff803u},
{{"BufferStoreSnorm8SpecialValues", "TBufferStoreSnorm8SpecialValues"},
F::k8_8_8_8SNorm, 4, {nan, inf, -inf, -0.0f}, 0x00817f00u},
{{"BufferStoreUscaled8SpecialValues", "TBufferStoreUscaled8SpecialValues"},
F::k8_8_8_8UScaled, 4, {nan, inf, -inf, -0.0f}, 0x0000ff00u},
{{"BufferStoreSscaled8SpecialValues", "TBufferStoreSscaled8SpecialValues"},
F::k8_8_8_8SScaled, 4, {nan, inf, -inf, -0.0f}, 0x00807f00u},
};
constexpr O store_opcodes[2][4] = {
{O::BUFFER_STORE_FORMAT_X, O::BUFFER_STORE_FORMAT_XY,
O::BUFFER_STORE_FORMAT_XYZ, O::BUFFER_STORE_FORMAT_XYZW},
{O::TBUFFER_STORE_FORMAT_X, O::TBUFFER_STORE_FORMAT_XY,
O::TBUFFER_STORE_FORMAT_XYZ, O::TBUFFER_STORE_FORMAT_XYZW},
};
std::vector<TestCase> cases;
for (const auto &input : inputs) {
for (const bool typed : {false, true}) {
TestCase test;
test.name = input.name[typed];
for (u32 component = 0; component < input.components; ++component) {
AppendVMovLiteral(&test.code, component,
std::bit_cast<u32>(input.values[component]));
}
AppendVMovU32(&test.code, 20, 4);
const u32 opcode = 3 + input.components;
if (typed) {
const u32 format = BufferFormat(input.format);
test.code.push_back(EncodeMtbuf0(opcode, format & 0xfu, format >> 4u));
test.code.push_back(EncodeMtbuf1(opcode, 0, 0, 20));
} else {
test.code.push_back(EncodeMubuf0(opcode));
test.code.push_back(EncodeMubuf1(0, 0, 20));
}
AppendEnd(&test.code);
test.initial = {0xdeadbeefu, 0xffffffffu, 0xcafef00du};
test.expected = {0xdeadbeefu, input.expected, 0xcafef00du};
test.user_data = MakeStructuredStorageBufferData(
0, 12, false, BufferFormat(input.format));
test.has_user_data = true;
test.opcodes = {O::V_MOV_B32, store_opcodes[typed][input.components - 1],
O::S_ENDPGM};
cases.push_back(std::move(test));
}
}
return cases;
}
TestCase BufferLoadFormatXyResource88UintExtractsBytes() {
using O = ShaderOpcode;
@@ -30255,7 +30437,7 @@ TestCase DsMiscVariants() {
return test;
}
TestCase DsFloatMinMaxUsesSeparateCompareOperand() {
TestCase DsFloatMinMaxIgnoresData1() {
using O = ShaderOpcode;
std::vector<u32> code;
@@ -30283,10 +30465,10 @@ TestCase DsFloatMinMaxUsesSeparateCompareOperand() {
AppendEnd(&code);
TestCase test;
test.name = "DsFloatMinMaxUsesSeparateCompareOperand";
test.name = "DsFloatMinMaxIgnoresData1";
test.code = code;
test.initial = std::vector<u32>(2, 0);
test.expected = {0x41100000u, 0x3f800000u};
test.expected = {0x40800000u, 0x40800000u};
test.opcodes = {O::V_MOV_B32, O::DS_WRITE_B32, O::DS_MIN_F32,
O::DS_MAX_F32, O::DS_READ_B32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
@@ -30297,6 +30479,100 @@ TestCase DsFloatMinMaxUsesSeparateCompareOperand() {
return test;
}
TestCase DsFloatMinMaxClasses(bool gds) {
using O = ShaderOpcode;
// Rows are memory, columns source: -inf, -normal, -denorm, -0, +0,
// +denorm, +normal, +inf, qNaN, sNaN. O/N select old/new; Q/R quiet them.
constexpr std::string_view minimum[] = {
"OOOOOOOOOR", "NNOOOOOOOR", "NNNNOOOOOR", "NNNOOOOOOR", "NNNNOOOOOR",
"NNNNNNOOOR", "NNNNNNOOOR", "NNNNNNNOOR", "NNNNNNNNOR", "QQQQQQQQQQ"};
constexpr std::string_view maximum[] = {
"ONNNNNNNOR", "OONNNNNNOR", "OOOONNNNOR", "OOOONNNNOR", "OOOOONNNOR",
"OOOOOONNOR", "OOOOOONNOR", "OOOOOOOOOR", "NNNNNNNNOR", "QQQQQQQQQQ"};
constexpr u32 old_values[] = {
0xff800000u, 0xc0000000u, 0x80000100u, 0x80000000u, 0u,
0x00000100u, 0x40000000u, 0x7f800000u, 0x7fc12345u, 0x7f812345u};
constexpr u32 sources[] = {
0xff800000u, 0xc0400000u, 0x80000001u, 0x80000000u, 0u,
0x00000001u, 0x40400000u, 0x7f800000u, 0xffc54321u, 0xff854321u};
const auto select = [](char choice, u32 old, u32 source) {
const u32 bits = choice == 'O' || choice == 'Q' ? old : source;
return choice == 'Q' || choice == 'R' ? bits | 0x00400000u : bits;
};
TestCase test;
test.name = gds ? "DsFloatMinMaxGdsClasses" : "DsFloatMinMaxLdsClasses";
for (u32 row = 0; row < std::size(old_values); ++row) {
for (u32 column = 0; column < std::size(sources); ++column) {
const auto old = old_values[row];
const auto source = sources[column];
test.initial.insert(test.initial.end(), {old, source, 0u, 0u});
test.expected.insert(test.expected.end(),
{old, source, select(minimum[row][column], old, source),
select(maximum[row][column], old, source)});
}
}
// Reversing positive denorm magnitudes still selects source for min and old for max.
test.initial.insert(test.initial.end(), {1u, 0x100u, 0u, 0u});
test.expected.insert(test.expected.end(), {1u, 0x100u, 0x100u, 1u});
const auto count = static_cast<u32>(test.initial.size() / 4u);
auto &code = test.code;
code.push_back(EncodeVop2(0x1a, 1, InlineU32(3), 0));
code.push_back(EncodeVop2(0x1a, 8, InlineU32(4), 0));
code.push_back(EncodeMubuf0(0x0d));
code.push_back(EncodeMubuf1(2, 0, 8));
for (u32 offset : {0u, 4u}) {
code.push_back(EncodeDs0(0x0d, offset, gds));
code.push_back(EncodeDs1(0, 2, 1));
}
code.push_back(EncodeDs0(0x12, 0, gds));
code.push_back(EncodeDs1Ex(0, 250, 3, 1)); // DATA1 is unused and uninitialized.
code.push_back(EncodeDs0(0x13, 4, gds));
code.push_back(EncodeDs1Ex(0, 251, 3, 1));
AppendVMovLiteral(&code, 6, 0xff800000u);
AppendVMovLiteral(&code, 7, 0x7f800000u);
code.push_back(EncodeSop1(0x04, 4, 126));
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
// Inactive updates must leave the table results untouched.
code.push_back(EncodeDs0(0x12, 0, gds));
code.push_back(EncodeDs1(0, 6, 1));
code.push_back(EncodeDs0(0x13, 4, gds));
code.push_back(EncodeDs1(0, 7, 1));
code.push_back(EncodeSop1(0x04, 126, 4));
// Active accesses immediately beyond the allocation must also be discarded.
AppendVMovU32(&code, 9, count * 8u);
code.push_back(EncodeDs0(0x12, 0, gds));
code.push_back(EncodeDs1(0, 6, 9));
code.push_back(EncodeDs0(0x13, 0, gds));
code.push_back(EncodeDs1(0, 7, 9));
for (u32 component = 0; component < 2u; ++component) {
code.push_back(EncodeDs0(0x36, component * 4u, gds));
code.push_back(EncodeDs1(4u + component, 0, 1));
}
code.push_back(EncodeMubuf0(0x1d, 8));
code.push_back(EncodeMubuf1(4, 12, 8));
AppendEnd(&code);
test.compute_info.threads_num[0] = count;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.compute_info.lds_size_dwords = count * 2u;
test.has_compute_info = true;
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::S_MOV_B64,
O::BUFFER_LOAD_DWORDX2, O::DS_WRITE_B32, O::DS_MIN_F32,
O::DS_MAX_F32, O::DS_READ_B32, O::BUFFER_STORE_DWORDX2,
O::S_ENDPGM};
test.required_spirv = {"OpAtomicLoad", "OpAtomicCompareExchange", "OpLoopMerge"};
test.forbidden_spirv = {"OpFOrdLessThan", "OpFOrdGreaterThan"};
if (gds) {
test.gds_initial.resize(count * 2u);
for (u32 i = 0; i < count; ++i) {
test.expected_gds.push_back(test.expected[i * 4u + 2u]);
test.expected_gds.push_back(test.expected[i * 4u + 3u]);
}
}
return test;
}
TestCase DsSwizzleInvalidSourceLaneZero() {
using O = ShaderOpcode;
@@ -33370,20 +33646,68 @@ TestCase ImageAtomicVariants() {
return test;
}
template <bool wide> TestCase ImageAtomicUMaxGlcAndExec() {
// Input and old texel: high DWORD ordering, unsigned sign boundary, and
// equal-high-DWORD ordering must all survive the RG32_UINT -> R64_UINT view.
constexpr uint64_t values[][2] = {
{0x0000000100000001ull, 0x00000000ffffffffull},
{0x00000000ffffffffull, 0x0000000100000001ull},
{0x8000000000000000ull, 0x7fffffffffffffffull},
{0x12345678ffffffffull, 0x1234567800000001ull},
{0xffffffffffffffffull, 0x8000000000000000ull},
};
constexpr u32 texel_dwords = wide ? 2 : 1;
// Input and old texel cover high-DWORD ordering, carry/wrap, the unsigned sign
// boundary, and equal-high-DWORD ordering through the RG32_UINT -> R64_UINT view.
constexpr uint64_t ImageAtomicInputs[][2] = {
{0x0000000100000001ull, 0x00000000ffffffffull},
{0x00000000ffffffffull, 0x0000000100000001ull},
{0x8000000000000000ull, 0x7fffffffffffffffull},
{0x12345678ffffffffull, 0x1234567800000001ull},
{0xffffffffffffffffull, 0x8000000000000000ull},
};
struct ImageAtomicIntegerCase {
u32 encoding;
ShaderOpcode opcode;
const char *name32;
const char *name64;
const char *spirv;
uint64_t expected64[std::size(ImageAtomicInputs)];
u32 expected32[std::size(ImageAtomicInputs)];
};
constexpr ImageAtomicIntegerCase ImageAtomicIntegerCases[] = {
{0x0fu, ShaderOpcode::IMAGE_ATOMIC_SWAP,
"ImageAtomicSwap32GlcAndExec", "ImageAtomicSwap64GlcAndExec", "OpAtomicExchange",
{0x0000000100000001ull, 0x00000000ffffffffull, 0x8000000000000000ull,
0x12345678ffffffffull, 0xffffffffffffffffull},
{0x00000001u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu}},
{0x11u, ShaderOpcode::IMAGE_ATOMIC_ADD,
"ImageAtomicAdd32GlcAndExec", "ImageAtomicAdd64GlcAndExec", "OpAtomicIAdd",
{0x0000000200000000ull, 0x0000000200000000ull, 0xffffffffffffffffull,
0x2468acf100000000ull, 0x7fffffffffffffffull},
{0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu}},
{0x15u, ShaderOpcode::IMAGE_ATOMIC_UMIN,
"ImageAtomicUMin32GlcAndExec", "ImageAtomicUMin64GlcAndExec", "OpAtomicUMin",
{0x00000000ffffffffull, 0x00000000ffffffffull, 0x7fffffffffffffffull,
0x1234567800000001ull, 0x8000000000000000ull},
{0x00000001u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u}},
{0x17u, ShaderOpcode::IMAGE_ATOMIC_UMAX,
"ImageAtomicUMax32GlcAndExec", "ImageAtomicUMax64GlcAndExec", "OpAtomicUMax",
{0x0000000100000001ull, 0x0000000100000001ull, 0x8000000000000000ull,
0x12345678ffffffffull, 0xffffffffffffffffull},
{0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu}},
{0x18u, ShaderOpcode::IMAGE_ATOMIC_AND,
"ImageAtomicAnd32GlcAndExec", "ImageAtomicAnd64GlcAndExec", "OpAtomicAnd",
{0x0000000000000001ull, 0x0000000000000001ull, 0x0000000000000000ull,
0x1234567800000001ull, 0x8000000000000000ull},
{0x00000001u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u}},
{0x19u, ShaderOpcode::IMAGE_ATOMIC_OR,
"ImageAtomicOr32GlcAndExec", "ImageAtomicOr64GlcAndExec", "OpAtomicOr",
{0x00000001ffffffffull, 0x00000001ffffffffull, 0xffffffffffffffffull,
0x12345678ffffffffull, 0xffffffffffffffffull},
{0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu}},
{0x1au, ShaderOpcode::IMAGE_ATOMIC_XOR,
"ImageAtomicXor32GlcAndExec", "ImageAtomicXor64GlcAndExec", "OpAtomicXor",
{0x00000001fffffffeull, 0x00000001fffffffeull, 0xffffffffffffffffull,
0x00000000fffffffeull, 0x7fffffffffffffffull},
{0xfffffffeu, 0xfffffffeu, 0xffffffffu, 0xfffffffeu, 0xffffffffu}},
};
TestCase ImageAtomicIntegerGlcAndExec(const ImageAtomicIntegerCase &atomic, bool wide) {
const u32 texel_dwords = wide ? 2 : 1;
TestCase test;
test.name = wide ? "ImageAtomicUMax64CapturedGlcAndExec"
: "ImageAtomicUMax32GlcAndExec";
test.name = wide ? atomic.name64 : atomic.name32;
test.user_data = MakeStorageTextureData(wide ? Prospero::BufferFormat::k32_32UInt
: Prospero::BufferFormat::k32UInt);
test.has_user_data = true;
@@ -33391,25 +33715,21 @@ template <bool wide> TestCase ImageAtomicUMaxGlcAndExec() {
test.expected_storage_image_r32ui = test.storage_image_r32ui;
auto &code = test.code;
code.push_back(EncodeSop1(0x04, 12, 126)); // Preserve EXEC.
for (u32 i = 0; i < std::size(values); ++i) {
const auto input = values[i][0];
const auto old = values[i][1];
for (u32 i = 0; i < std::size(ImageAtomicInputs); ++i) {
const auto input = ImageAtomicInputs[i][0];
const auto old = ImageAtomicInputs[i][1];
const bool glc = i != 0 && i != 4;
AppendVMovU32(&code, 0, i & 3u);
AppendVMovU32(&code, 1, i >> 2u);
AppendVMovLiteral(&code, 2, static_cast<u32>(input));
AppendVMovLiteral(&code, 3, static_cast<u32>(input >> 32u));
// Captured PS5 instruction: f05c0308 00000200, DMASK=3, VDATA=v[2:3].
code.insert(code.end(), {0xf05c0108u | (wide ? 0x200u : 0u) |
(glc ? 0x2000u : 0u),
0x00000200u});
code.push_back(EncodeMimg0(atomic.encoding, wide ? 3u : 1u, 0, glc));
code.push_back(EncodeMimg1(2, 0));
AppendStoreVgpr(&code, 2, i * 2);
AppendStoreVgpr(&code, 3, i * 2 + 1);
test.expected.push_back(static_cast<u32>(glc ? old : input));
test.expected.push_back(static_cast<u32>((wide && glc ? old : input) >> 32u));
const uint64_t result = wide ? std::max(input, old)
: std::max(static_cast<u32>(input),
static_cast<u32>(old));
const uint64_t result = wide ? atomic.expected64[i] : atomic.expected32[i];
for (u32 word = 0; word < texel_dwords; ++word) {
test.storage_image_r32ui[i * texel_dwords + word] =
static_cast<u32>(old >> (word * 32u));
@@ -33417,23 +33737,30 @@ template <bool wide> TestCase ImageAtomicUMaxGlcAndExec() {
static_cast<u32>(result >> (word * 32u));
}
}
// A leaked update must change the inactive texel for every operation.
const bool clear_bits = atomic.opcode == ShaderOpcode::IMAGE_ATOMIC_AND ||
atomic.opcode == ShaderOpcode::IMAGE_ATOMIC_UMIN;
const u32 inactive = clear_bits ? 0xffffffffu : 0u;
for (u32 word = 0; word < texel_dwords; ++word) {
test.storage_image_r32ui[5u * texel_dwords + word] = inactive;
test.expected_storage_image_r32ui[5u * texel_dwords + word] = inactive;
}
AppendVMovU32(&code, 0, 1);
AppendVMovU32(&code, 1, 1); // Untouched zero texel, so an EXEC leak is observable.
AppendVMovU32(&code, 1, 1);
AppendVMovLiteral(&code, 2, 0xabcdef01u);
AppendVMovLiteral(&code, 3, 0x87654321u);
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
code.insert(code.end(), {0xf05c2108u | (wide ? 0x200u : 0u), 0x00000200u});
code.push_back(EncodeMimg0(atomic.encoding, wide ? 3u : 1u, 0, true));
code.push_back(EncodeMimg1(2, 0));
code.push_back(EncodeSop1(0x04, 126, 12));
AppendStoreVgpr(&code, 2, std::size(values) * 2);
AppendStoreVgpr(&code, 3, std::size(values) * 2 + 1);
AppendStoreVgpr(&code, 2, std::size(ImageAtomicInputs) * 2);
AppendStoreVgpr(&code, 3, std::size(ImageAtomicInputs) * 2 + 1);
test.expected.insert(test.expected.end(), {0xabcdef01u, 0x87654321u});
AppendEnd(&code);
test.opcodes = {ShaderOpcode::V_MOV_B32, ShaderOpcode::S_MOV_B64,
ShaderOpcode::IMAGE_ATOMIC_UMAX, ShaderOpcode::BUFFER_STORE_DWORD,
ShaderOpcode::S_ENDPGM};
test.required_spirv = {"OpAtomicUMax", "OpImageTexelPointer",
wide ? "R64ui" : "R32ui"};
if constexpr (wide) {
atomic.opcode, ShaderOpcode::BUFFER_STORE_DWORD, ShaderOpcode::S_ENDPGM};
test.required_spirv = {atomic.spirv, "OpImageTexelPointer", wide ? "R64ui" : "R32ui"};
if (wide) {
test.required_spirv.push_back("Int64ImageEXT");
test.required_spirv.push_back("SPV_EXT_shader_image_int64");
}
@@ -34214,6 +34541,8 @@ std::vector<TestCase> MakeCases() {
AddCase(VectorDppBankMaskPreservesDestination);
AddCase(VectorDppBoundsControlZeroPreservesDestination);
AddCase(Vop3FmacF32NegatedSourceAccumulates);
AddCase(VectorMadF32RoundsProduct);
AddCase(VectorMadF32FlushesDenorms);
AddCase(Vop3LdexpSourceModifier);
AddCase(Vop1MoveRelSource);
AddCase(Vop1MoveRelDestination);
@@ -34347,6 +34676,9 @@ std::vector<TestCase> MakeCases() {
AddCase(BufferStoreFormatXyzwFloat16ConvertsComponents);
AddCase(BufferStoreFormatXyzwSnorm16CapturedSkinningVectors);
AddCase(BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords);
for (auto &test : FormattedStoreConversionCases()) {
cases.push_back(std::move(test));
}
AddCase(BufferLoadFormatXResource8UintZeroExtendsByte);
AddCase(BufferLoadFormatXyResource88UintExtractsBytes);
AddCase(BufferLoadFormatXyResource8888UnormConvertsFirstTwoComponents);
@@ -34443,7 +34775,9 @@ std::vector<TestCase> MakeCases() {
}
}
AddCase(DsMiscVariants);
AddCase(DsFloatMinMaxUsesSeparateCompareOperand);
AddCase(DsFloatMinMaxIgnoresData1);
AddCase([] { return DsFloatMinMaxClasses(false); });
AddCase([] { return DsFloatMinMaxClasses(true); });
AddCase(DsSwizzleInvalidSourceLaneZero);
AddCase(DsPermuteCapturedExecOffsetAndWrap);
AddCase(DsPermuteWave64UsesIndependentHalves);
@@ -34519,8 +34853,10 @@ std::vector<TestCase> MakeCases() {
AddCase(ImageAtomicSwapReturnsPreviousTexel);
AddCase(ImageStoreAndAtomicUseSeparateBindings);
AddCase(ImageAtomicVariants);
AddCase(ImageAtomicUMaxGlcAndExec<false>);
AddCase(ImageAtomicUMaxGlcAndExec<true>);
for (const auto &atomic : ImageAtomicIntegerCases) {
cases.push_back(ImageAtomicIntegerGlcAndExec(atomic, false));
cases.push_back(ImageAtomicIntegerGlcAndExec(atomic, true));
}
AddCase(ImageAtomicFloatGlcAndExec<true>);
AddCase(ImageAtomicFloatGlcAndExec<false>);
AddCase(ImageAtomicFloatSpecialValues<true>);
@@ -39536,6 +39872,9 @@ int main(int argc, char **argv) {
RunCase(&vulkan, BufferStoreFormatXyzwFloat16ConvertsComponents());
RunCase(&vulkan, BufferStoreFormatXyzwSnorm16CapturedSkinningVectors());
RunCase(&vulkan, BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords());
for (const auto &test : FormattedStoreConversionCases()) {
RunCase(&vulkan, test);
}
RunCase(&vulkan, BufferStoreFormatXResource16UintWritesHalfword());
RunCase(&vulkan, BufferStoreFormatXResource16UintPreservesAdjacentLanes());
RunCase(&vulkan, BufferStoreFormatXyResource88UintWritesBytes());
@@ -39835,8 +40174,10 @@ int main(int argc, char **argv) {
}
if (argc == 2 && std::strcmp(argv[1], "--image-atomic64-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, ImageAtomicUMaxGlcAndExec<false>());
RunCase(&vulkan, ImageAtomicUMaxGlcAndExec<true>());
for (const auto &atomic : ImageAtomicIntegerCases) {
RunCase(&vulkan, ImageAtomicIntegerGlcAndExec(atomic, false));
RunCase(&vulkan, ImageAtomicIntegerGlcAndExec(atomic, true));
}
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--ds-atomics-only") == 0) {
@@ -39884,6 +40225,14 @@ int main(int argc, char **argv) {
RunCase(&vulkan, VectorVop3IntegerOps());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--mad-f32-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, VectorMadF32RoundsProduct());
RunCase(&vulkan, VectorMadF32FlushesDenorms());
RunCase(&vulkan, Vop3FmacF32NegatedSourceAccumulates());
RunCase(&vulkan, VectorFloatArithmeticOps());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--zero-shift-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, ScalarZeroShiftWithRuntimeCount());
+35 -49
View File
@@ -4413,25 +4413,32 @@ void TestImageAtomicWidthDecoder() {
decoded.dst.reg == 2u && decoded.dmask == 3u && !decoded.glc,
"captured image atomic lost its 64-bit data width");
for (const auto opcode : {0x10u, 0x17u}) {
for (const auto opcode : {0x0fu, 0x10u, 0x11u, 0x14u, 0x15u, 0x16u,
0x17u, 0x18u, 0x19u, 0x1au, 0x1eu, 0x1fu}) {
const bool compare_swap = opcode == 0x10u;
const bool supports_64 = opcode != 0x10u && opcode != 0x14u &&
opcode != 0x16u && opcode != 0x1eu && opcode != 0x1fu;
for (uint32_t mask = 0; mask < 16u; ++mask) {
const uint32_t words[] = {EncodeMimg0(opcode, mask), captured[1]};
DecodeInstruction(words, 0, decoded);
const bool compare_swap = opcode == 0x10u;
const bool supported = mask == (compare_swap ? 3u : 1u) ||
(!compare_swap && mask == 3u);
Check((decoded.opcode != Opcode::UNSUPPORTED) == supported,
"image atomic accepted an invalid or unsupported width mask");
if (supported) {
Check(decoded.data_bits == (!compare_swap && mask == 3u ? 64u : 32u),
"image atomic DMASK selected the wrong data width");
for (const bool glc : {false, true}) {
const uint32_t words[] = {EncodeMimg0(opcode, mask, glc), captured[1]};
DecodeInstruction(words, 0, decoded);
const bool supported = mask == (compare_swap ? 3u : 1u) ||
(supports_64 && mask == 3u);
Check((decoded.opcode != Opcode::UNSUPPORTED) == supported,
"image atomic accepted an invalid or unsupported width mask");
if (supported) {
const auto bits = !compare_swap && mask == 3u ? 64u : 32u;
Check(decoded.data_bits == bits && decoded.glc == glc &&
decoded.data_dwords == (compare_swap ? 2u : bits / 32u),
"image atomic DMASK selected the wrong data width or return mode");
}
const uint32_t d16_words[] = {words[0], words[1] | (1u << 31u)};
DecodeInstruction(d16_words, 0, decoded);
Check(decoded.opcode == Opcode::UNSUPPORTED,
"image atomic incorrectly accepted D16 data");
}
}
}
const uint32_t unsupported[] = {EncodeMimg0(0x11u, 3u), captured[1]};
DecodeInstruction(unsupported, 0, decoded);
Check(decoded.opcode == Opcode::UNSUPPORTED && decoded.data_bits == 64u,
"unsupported 64-bit image atomic silently decoded as 32-bit");
}
void TestNewShaderDecoderArchitecture() {
@@ -7741,40 +7748,18 @@ void TestNewShaderRecompilerDsAddtidTranslation() {
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerDsFloatMinMaxTranslation() {
const uint32_t shader[] = {
EncodeDs0(0x12, 4), EncodeDs1Ex(0, 9, 7, 1), // ds_min_f32 v7, v9, v1
EncodeDs0(0x13, 8), EncodeDs1Ex(0, 10, 8, 1), // ds_max_f32 v8, v10, v1
0xbf810000u,
};
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
auto result = RecompileForTest(shader, options);
Check((result.decoded_dump.find("ds_min_f32") != std::string::npos),
"new decoder did not decode DS float min");
Check((result.decoded_dump.find("ds_max_f32") != std::string::npos),
"new decoder did not decode DS float max");
Check((result.ir_dump.find("DsMinF32 null, v7, v1") != std::string::npos),
"DS float min did not lower to explicit IR");
Check((result.ir_dump.find("DsMaxF32 null, v8, v1") != std::string::npos),
"DS float max did not lower to explicit IR");
Check((result.ir_dump.find("v9") != std::string::npos),
"DS float min did not retain DATA1 compare operand");
Check((result.ir_dump.find("v10") != std::string::npos),
"DS float max did not retain DATA1 compare operand");
Check(SpirvContainsOpcode(result.spirv, 12),
"SPIR-V binary does not contain OpExtInst");
Check(SpirvContainsOpcode(result.spirv, 61),
"SPIR-V binary does not contain OpLoad");
Check(SpirvContainsOpcode(result.spirv, 62),
"SPIR-V binary does not contain OpStore");
Check(SpirvContainsOpcode(result.spirv, 65),
"SPIR-V binary does not contain OpAccessChain");
Check(SpirvContainsOpcode(result.spirv, 124),
"SPIR-V binary does not contain OpBitcast");
CheckSpirvBinaryValidates(result.spirv);
void TestDsFloatMinMaxDecoder() {
using namespace ShaderRecompiler::Decoder;
for (bool max_value : {false, true}) {
const uint32_t shader[] = {
EncodeDs0(max_value ? 0x13 : 0x12, 4), EncodeDs1Ex(0, 250, 7, 1)};
Instruction decoded;
DecodeInstruction(shader, 0u, decoded);
Check(decoded.opcode == (max_value ? Opcode::DS_MAX_F32 : Opcode::DS_MIN_F32) &&
decoded.src_count == 2u && decoded.src0.reg == 1u &&
decoded.src1.reg == 7u && decoded.offset == 4u,
"DS float min/max must read only address and DATA0");
}
}
void TestNewShaderRecompilerCfgStraightLine() {
@@ -14805,6 +14790,7 @@ int main() {
// here.
TestScalarAshrI64Decoder();
TestImageAtomicWidthDecoder();
TestDsFloatMinMaxDecoder();
TestNewShaderDecoderArchitecture();
TestImageAddressOperands();
TestSopkCompareImmediateExtension();