shader: emit native 64-bit operations where compatible

This commit is contained in:
nmzik
2026-10-05 20:41:03 +04:00
parent 77d7722173
commit 5b826c6e4a
16 changed files with 500 additions and 436 deletions
+194 -28
View File
@@ -4613,6 +4613,24 @@ public:
sizeof(partial_unmap_survivor_value),
partial_unmap_survivor_value, false);
resources.UnmapMemory(base + 0x8000, 0x4000);
Require(
name, "partial-invalidation keeps disjoint bytes dirty",
resources.IsMapped(base + partial_unmap_survivor_offset,
sizeof(partial_unmap_survivor_value)) &&
!cache.IsRegionCpuModified(
base + partial_unmap_survivor_offset,
sizeof(partial_unmap_survivor_value)) &&
cache.IsRegionGpuModified(base + partial_unmap_survivor_offset,
sizeof(partial_unmap_survivor_value)) &&
cache.HasGpuDirtyBytes(base + partial_unmap_survivor_offset,
sizeof(partial_unmap_survivor_value)) &&
!cache.HasGpuDirtyBytes(base + partial_unmap_offset,
sizeof(partial_unmap_value)),
"partial invalidation read back an unrelated mapped page");
Require(name, "partial-invalidation survivor read fault",
resources.HandleFault(PageFaultAccess::Read,
base + partial_unmap_survivor_offset),
"CPU read did not publish the retained GPU-dirty page");
uint32_t partial_unmap_survivor_backing = 0;
std::memcpy(&partial_unmap_survivor_backing,
memory + partial_unmap_survivor_offset,
@@ -4630,7 +4648,8 @@ public:
!cache.HasGpuDirtyBytes(base + partial_unmap_survivor_offset,
sizeof(partial_unmap_survivor_value)) &&
partial_unmap_survivor_backing == partial_unmap_survivor_value,
"widened partial invalidation mishandled ownership or backing");
"partial invalidation or subsequent CPU read mishandled ownership or "
"backing");
resources.MapMemory(base + 0x8000, 0x4000);
auto survivor =
cache.ObtainBuffer(base + partial_unmap_survivor_offset,
@@ -20312,6 +20331,114 @@ TestCase Scalar64BitOps() {
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase ScalarDynamic64BitOps() {
using O = ShaderOpcode;
struct Input {
uint64_t lhs;
uint64_t rhs;
u32 count;
u32 offset;
};
constexpr Input inputs[] = {
{0, UINT64_MAX, 0, 0},
{UINT64_MAX, 0, 63, 0},
{0x8000000000000000ull, 1, 64, 0},
{0x0123456789abcdefull, 0xfedcba9876543210ull, 127, 0},
{UINT64_MAX, 0xffffffffull, 64, 1},
{0x0123456789abcdefull, 0x0123456789abcdefull, 63, 32},
{UINT64_MAX, 0x100000000ull, 127, 63},
{0, 0, 0, 127},
{0x8000000000000001ull, UINT64_MAX, UINT32_MAX, 64},
};
TestCase test;
test.name = "ScalarDynamic64BitOps";
for (const auto &input : inputs) {
test.initial.insert(test.initial.end(),
{u32(input.lhs), u32(input.lhs >> 32), u32(input.rhs),
u32(input.rhs >> 32), input.count, input.offset,
((input.count & 127u) << 16) | input.offset});
}
test.expected = test.initial;
auto &code = test.code;
const auto store = [&](uint64_t value, bool scc) {
const auto output = static_cast<u32>(test.expected.size());
code.push_back(EncodeSMovB32(18, 253));
AppendStoreSgprPair(&code, 16, output);
AppendStoreSgpr(&code, 18, output + 2);
test.expected.insert(test.expected.end(),
{u32(value), u32(value >> 32), u32(scc)});
};
for (u32 i = 0; i < std::size(inputs); ++i) {
const auto [lhs, rhs, count, offset] = inputs[i];
for (u32 word = 0; word < 7; ++word) {
AppendVMovU32(&code, 30, (i * 7 + word) * sizeof(u32));
AppendBufferLoadDword(&code, 1, 30);
code.push_back(EncodeVop1(0x02, 8 + word, Vgpr(1)));
}
for (const auto [opcode, value] :
std::array<std::pair<u32, uint64_t>, 8>{{{0x0f, lhs & rhs},
{0x11, lhs | rhs},
{0x13, lhs ^ rhs},
{0x15, lhs & ~rhs},
{0x17, lhs | ~rhs},
{0x19, ~(lhs & rhs)},
{0x1b, ~(lhs | rhs)},
{0x1d, ~(lhs ^ rhs)}}}) {
code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(value != 0)));
code.push_back(EncodeSop2(opcode, 16, 8, 10));
store(value, value != 0);
}
for (const bool select_lhs : {false, true}) {
code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(!select_lhs)));
code.push_back(EncodeSop2(0x0b, 16, 8, 10));
store(select_lhs ? lhs : rhs, select_lhs);
}
// BFM preserves SCC; BFE sets it from the entire extracted result.
code.push_back(EncodeSop2(0x25, 16, 12, 13));
store(((uint64_t{1} << (count & 63u)) - 1u) << (offset & 63u), true);
code.push_back(EncodeSop2(0x29, 16, 8, 14));
const auto width = std::min(count & 127u, 64u - (offset & 63u));
const auto mask = width == 64 ? UINT64_MAX : (uint64_t{1} << width) - 1u;
const auto extracted = (lhs >> (offset & 63u)) & mask;
store(extracted, extracted != 0);
for (const bool set : {false, true}) {
code.push_back(EncodeSop1(0x04, 16, 8));
code.push_back(EncodeSop1(set ? 0x1e : 0x1c, 16, 12));
const auto bit = uint64_t{1} << (count & 63u);
store(set ? lhs | bit : lhs & ~bit, extracted != 0);
}
code.push_back(EncodeSop1(0x2d, 16, 8));
uint64_t quads = 0;
for (u32 quad = 0; quad < 16; ++quad) {
quads |= uint64_t((lhs >> (quad * 4) & 15u) != 0) << quad;
}
store(quads, quads != 0);
code.push_back(EncodeSop1(0x3b, 16, 8));
uint64_t replicated = 0;
for (u32 bit = 0; bit < 32; ++bit) {
replicated |= ((lhs >> bit) & 1u) * (uint64_t{3} << (2 * bit));
}
store(replicated, quads != 0);
code.push_back(EncodeSMovB32(17, InlineU32(0)));
code.push_back(EncodeSop1(0x10, 16, 8));
store(std::popcount(lhs), lhs != 0);
}
AppendEnd(&code);
test.initial.resize(test.expected.size());
test.opcodes = {
O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32,
O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_AND_B64,
O::S_OR_B64, O::S_XOR_B64, O::S_ANDN2_B64,
O::S_ORN2_B64, O::S_NAND_B64, O::S_NOR_B64,
O::S_XNOR_B64, O::S_CSELECT_B64, O::S_BFM_B64,
O::S_BFE_U64, O::S_MOV_B64, O::S_BITSET0_B64,
O::S_BITSET1_B64, O::S_QUADMASK_B64, O::S_BITREPLICATE_B64_B32,
O::S_BCNT1_I32_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpTypeInt 64 0", "OpBitwiseAnd", "OpBitwiseOr",
"OpBitwiseXor"};
return test;
}
uint64_t ReferenceAshrI64(uint64_t bits, u32 count) {
const auto shift = count & 63u;
// Complementing negative values expresses sign fill using unsigned shifts.
@@ -20385,8 +20512,7 @@ TestCase ScalarAshrI64Edges(bool dynamic) {
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.decoded_counts = {
{"S_ASHR_I64", std::size(sources) * std::size(counts)}};
test.required_spirv = {"OpShiftRightArithmetic"};
test.forbidden_spirv = {"OpTypeInt 64"};
test.required_spirv = {"OpShiftRightArithmetic", "OpTypeInt 64 0"};
return test;
}
@@ -20487,7 +20613,7 @@ TestCase ScalarAshrI64OperandsAndAliases() {
O::S_ASHR_I64, O::V_MOV_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.decoded_counts = {{"S_ASHR_I64", instructions}};
test.forbidden_spirv = {"OpTypeInt 64"};
test.required_spirv.push_back("OpTypeInt 64 0");
return test;
}
@@ -20549,7 +20675,6 @@ TestCase ScalarAshrI64Masks(u32 wave_size) {
O::S_ENDPGM};
test.decoded_counts = {{"S_ASHR_I64 vcc_lo, vcc_lo", std::size(cases)},
{"S_ASHR_I64 exec_lo, exec_lo", std::size(cases)}};
test.forbidden_spirv = {"OpTypeInt 64"};
test.compute_info.threads_num[0] = wave_size;
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
@@ -21871,8 +21996,7 @@ TestCase VectorVop3LshrrevB64Captured() {
test.expected = {0x789abcdeu, 0x00123456u};
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32, O::V_LSHRREV_B64,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpShiftRightLogical", "OpSelect"};
test.forbidden_spirv = {"OpTypeInt 64"};
test.required_spirv = {"OpShiftRightLogical"};
return test;
}
@@ -21899,8 +22023,7 @@ TestCase VectorVop3LshlrevB64Captured() {
test.expected = {33u, 0u, 2u, 1u, 0u};
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_LSHLREV_B64, O::V_ADD_NC_U32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpShiftLeftLogical", "OpSelect"};
test.forbidden_spirv = {"OpTypeInt 64"};
test.required_spirv = {"OpShiftLeftLogical", "OpTypeInt 64 0"};
return test;
}
@@ -21908,7 +22031,7 @@ TestCase VectorDynamicU64ShiftEdges() {
using O = ShaderOpcode;
TestCase test;
test.name = "VectorDynamicU64ShiftEdges";
test.initial = {0u, 1u, 31u, 32u, 33u, 63u, 64u, 65u};
test.initial = {0u, 1u, 31u, 32u, 33u, 63u, 64u, 65u, 127u, UINT32_MAX};
test.expected = test.initial;
auto& code = test.code;
for (const uint64_t source : {uint64_t{0}, uint64_t{1},
@@ -21937,8 +22060,8 @@ TestCase VectorDynamicU64ShiftEdges() {
test.initial.resize(test.expected.size());
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_LSHLREV_B64,
O::V_LSHRREV_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpShiftLeftLogical", "OpShiftRightLogical", "OpSelect"};
test.forbidden_spirv = {"OpTypeInt 64"};
test.required_spirv = {"OpShiftLeftLogical", "OpShiftRightLogical",
"OpTypeInt 64 0"};
return test;
}
@@ -22471,23 +22594,53 @@ TestCase VectorVop3BSubCoU32UsesRdna2Opcode310() {
TestCase VectorMadU64U32UnsignedCarryOut() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovLiteral(&code, 1, 0xffffffffu);
AppendVMovU32(&code, 2, 2);
AppendVMovU32(&code, 4, 2);
AppendVMovLiteral(&code, 5, 0xffffffffu);
AppendVop3B(&code, 0x176u, 10, 20, Vgpr(1), Vgpr(2), Vgpr(4));
AppendStoreVgpr(&code, 10, 0);
AppendStoreVgpr(&code, 11, 1);
AppendStoreSgprPair(&code, 20, 2);
struct Input {
u32 lhs;
u32 rhs;
uint64_t addend;
};
constexpr Input inputs[] = {
{0, UINT32_MAX, UINT64_MAX},
{UINT32_MAX, 2, 0xffffffff00000002ull},
{UINT32_MAX, UINT32_MAX, UINT64_MAX},
{0x80000000u, 0x80000000u, 0x4000000000000000ull},
{3, 5, 7},
};
TestCase test;
test.name = "VectorMadU64U32UnsignedCarryOut";
for (const auto &input : inputs) {
test.initial.insert(
test.initial.end(),
{input.lhs, input.rhs, u32(input.addend), u32(input.addend >> 32)});
}
test.expected = test.initial;
auto &code = test.code;
for (u32 i = 0; i < std::size(inputs); ++i) {
for (u32 word = 0; word < 4; ++word) {
AppendVMovU32(&code, 30, (i * 4 + word) * sizeof(u32));
AppendBufferLoadDword(&code, word < 2 ? word + 1 : word + 2, 30);
}
AppendVop3B(&code, 0x176u, 10, 20, Vgpr(1), Vgpr(2), Vgpr(4));
const auto output = static_cast<u32>(test.expected.size());
AppendStoreVgpr(&code, 10, output);
AppendStoreVgpr(&code, 11, output + 1);
AppendStoreSgprPair(&code, 20, output + 2);
const auto product = uint64_t(inputs[i].lhs) * inputs[i].rhs;
const auto sum = product + inputs[i].addend;
test.expected.insert(test.expected.end(),
{u32(sum), u32(sum >> 32), u32(sum < product), 0});
}
AppendEnd(&code);
return {"VectorMadU64U32UnsignedCarryOut",
code,
{},
{0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u},
{O::V_MOV_B32, O::V_MAD_U64_U32, 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_MAD_U64_U32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
// NVIDIA rejected the native widening multiply in a large Astro's Playroom
// vertex shader. Keep its 32x32 product portable and its 64-bit addition
// native.
test.required_spirv = {"OpTypeInt 64 0", "OpUMulExtended", "OpIMul %uint",
"OpIAdd %ulong", "OpULessThan"};
test.forbidden_spirv = {"OpIMul %ulong", "OpIMul %long"};
return test;
}
TestCase VectorLaneAndPackedOps() {
@@ -34689,6 +34842,7 @@ std::vector<TestCase> MakeCases() {
AddCase(ScalarBfeI32CapturedRawSignExtends);
AddCase(BitfieldExtractWidthPastEndEdges);
AddCase(Scalar64BitOps);
AddCase(ScalarDynamic64BitOps);
cases.push_back(ScalarAshrI64Edges(false));
cases.push_back(ScalarAshrI64Edges(true));
AddCase(ScalarAshrI64OperandsAndAliases);
@@ -40494,6 +40648,18 @@ int main(int argc, char **argv) {
vulkan.CheckGpuSuspendPoint();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--native64-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, ScalarDynamic64BitOps());
RunCase(&vulkan, VectorDynamicU64ShiftEdges());
RunCase(&vulkan, ScalarAshrI64Edges(false));
RunCase(&vulkan, ScalarAshrI64Edges(true));
RunCase(&vulkan, VectorCompareInteger64Edges());
RunCase(&vulkan, VectorMadU64U32UnsignedCarryOut());
RunCase(&vulkan, ScalarWqmB64SelectsSccDomain());
RunCase(&vulkan, ScalarWqmB64PreservesPartialMasks());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--alignbyte-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, VectorAlignByteUsesTwoBitByteOffset());
+88 -19
View File
@@ -7942,7 +7942,7 @@ void TestNewShaderRecompilerStructuredU64Phi() {
const auto after = MeasureSpirv(spirv);
Check(after.phis == before.phis + 1u &&
after.function_variables == before.function_variables,
"structured U64 Phi was not emitted as a native vector OpPhi");
"structured U64 Phi was not emitted as a native scalar OpPhi");
}
void TestNewShaderRecompilerCfgTerminalExitMergePS() {
@@ -9866,7 +9866,7 @@ void TestNewShaderRecompilerPlanningOnlyLoads() {
"planning-only loads or references emitted GPU operands/instructions");
}
void TestNewShaderRecompilerU64PairTranslation() {
void TestNewShaderRecompilerNativeU64Translation() {
using namespace ShaderRecompiler;
auto options = MakeCompileOptions(ShaderType::Compute);
@@ -9996,20 +9996,23 @@ void TestNewShaderRecompilerU64PairTranslation() {
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, options.input_info);
CheckSpirvBinaryValidates(spirv);
const auto source = DisassembleSpirvBinary(spirv);
Check((source.find("OpCapability Int64") == std::string::npos) &&
(source.find("OpTypeInt 64") == std::string::npos),
"portable pair-U64 translation introduced native shader Int64");
Check(SpirvInstructionOpcodeCount(spirv, 149u) == 1u,
"pair-U64 addition did not use exactly one carry instruction");
Check(SpirvInstructionOpcodeCount(spirv, 154u) == 1u &&
SpirvInstructionOpcodeCount(spirv, 155u) == 1u,
"pair-U64 equality did not reduce its vector comparison with Any/All");
Check(source.find("OpCapability Int64") != std::string::npos &&
source.find("OpTypeInt 64 0") != std::string::npos &&
SpirvSourceHasInstructionUsing(source, "OpIAdd", "%ulong") &&
SpirvSourceHasInstructionUsing(source, "OpIMul", "%ulong") &&
SpirvSourceHasInstructionUsing(source, "OpBitwiseAnd", "%ulong"),
"U64 arithmetic did not emit native 64-bit scalar instructions");
Check(SpirvInstructionOpcodeCount(spirv, spv::OpIAddCarry) == 0u &&
SpirvInstructionOpcodeCount(spirv, spv::OpUMulExtended) == 0u &&
SpirvInstructionOpcodeCount(spirv, spv::OpAny) == 0u &&
SpirvInstructionOpcodeCount(spirv, spv::OpAll) == 0u,
"native U64 arithmetic retained split-word operations");
const auto direct_metrics = MeasureSpirv(spirv);
Check(
direct_metrics.words <= 940u && direct_metrics.instructions <= 199u &&
direct_metrics.type_vectors == 2u && direct_metrics.phis == 0u &&
direct_metrics.function_variables == 0u,
"portable pair-U64 translation exceeded its declaration/code-size ratchet");
// The same fixture required 864 words and 182 instructions with split U64s.
Check(direct_metrics.words <= 285u && direct_metrics.instructions <= 67u &&
direct_metrics.phis == 0u &&
direct_metrics.function_variables == 0u,
"native U64 translation exceeded its declaration/code-size ratchet");
const uint32_t dispatcher_shader[] = {
EncodeSopp(0x05, 2), EncodeSopp(0x02, 0), EncodeSopp(0x05, 0xfffeu),
@@ -10034,7 +10037,39 @@ void TestNewShaderRecompilerU64PairTranslation() {
Check(after.function_variables == before.function_variables + 1u &&
after.loads == before.loads + 1u &&
after.stores == before.stores + 1u,
"dispatcher did not use one canonical U64 vector spill slot");
"dispatcher did not use one canonical U64 scalar spill slot");
}
void TestVertexMadU64UsesPortableProduct() {
using StageInputKind = ShaderRecompiler::IR::StageInputKind;
for (const bool dynamic_addend : {false, true}) {
const uint32_t shader[] = {
EncodeVop1(0x01, 6,
8 + 256), // Dynamic addend is [VertexIndex, InstanceIndex].
EncodeVop3Word0Sdst(0x176, 0, 20),
EncodeVop3Word1(5 + 256, 8 + 256, dynamic_addend ? 5 + 256 : 128),
EncodeExp0(0x0c, 0x3),
EncodeExp1(0, 1, 0, 0), // POS0.xy keeps both product words live.
EncodeSopp(0x01),
};
const auto result =
RecompileForTest(shader, MakeCompileOptions(ShaderType::Vertex));
Check(ProgramHasInput(result.program, StageInputKind::VertexIndex) &&
ProgramHasInput(result.program, StageInputKind::InstanceIndex),
"vertex MAD fixture lost its runtime multiplication operands");
CheckSpirvBinaryValidates(result.spirv);
const auto source = DisassembleSpirvBinary(result.spirv);
// Large vertex shaders on NVIDIA rejected native widening multiplication.
Check(SpirvInstructionOpcodeCount(result.spirv, spv::OpUMulExtended) ==
1u &&
SpirvSourceHasInstructionUsing(source, "OpIMul", "%uint") &&
!SpirvSourceHasInstructionUsing(source, "OpIMul", "%ulong"),
"vertex MAD lost its portable 32x32 product");
Check(!dynamic_addend ||
SpirvSourceHasInstructionUsing(source, "OpIAdd", "%ulong"),
"vertex MAD split its live 64-bit addition into word operations");
}
}
void TestComputeDispatchWaveSize() {
@@ -12131,7 +12166,8 @@ void TestU64ShiftConstantPropagation() {
std::vector<ShiftCase> shift_cases;
for (const uint64_t source :
{uint64_t{0x0123456789abcdefull}, uint64_t{0xf123456789abcdefull}}) {
for (const uint32_t count : {0u, 1u, 31u, 32u, 33u, 63u, 64u, 65u}) {
for (const uint32_t count :
{0u, 1u, 31u, 32u, 33u, 63u, 64u, 65u, 127u, UINT32_MAX}) {
const uint32_t amount = count & 63u;
shift_cases.push_back({shift_ir.Emit(ValueOpcode::ShiftLeftLogical64,
{Value(source), Value(count)}),
@@ -12150,10 +12186,41 @@ void TestU64ShiftConstantPropagation() {
const auto value = test.value.Resolve();
Check(value.IsImmediate() && value.GetType() == Type::U64 &&
value.U64() == test.expected,
"pair-U64 shift propagation violated the masked RDNA2 count");
"U64 shift propagation violated the masked RDNA2 count");
}
}
void TestU64RegisterPairRoundTrip() {
using namespace ShaderRecompiler::IR;
Program program;
program.block_storage.push_back(std::make_unique<Block>());
program.blocks.push_back(program.block_storage.back().get());
IREmitter ir(program.blocks.front());
const auto lane = ir.Emit(ValueOpcode::LaneId);
const auto pair = ir.Emit(ValueOpcode::CompositeConstructU64, {lane, lane});
const auto first = ir.Emit(ValueOpcode::IAdd64, {pair, Value(uint64_t{1})});
const auto second = ir.Emit(ValueOpcode::IAdd64, {pair, Value(uint64_t{2})});
const auto low =
ir.Emit(ValueOpcode::CompositeExtractU64, {first, Value(0u)});
const auto high =
ir.Emit(ValueOpcode::CompositeExtractU64, {first, Value(1u)});
const auto other_high =
ir.Emit(ValueOpcode::CompositeExtractU64, {second, Value(1u)});
const auto restored =
ir.Emit(ValueOpcode::CompositeConstructU64, {low, high});
const auto swapped = ir.Emit(ValueOpcode::CompositeConstructU64, {high, low});
const auto mixed =
ir.Emit(ValueOpcode::CompositeConstructU64, {low, other_high});
ConstantPropagationPass(program.blocks);
Check(restored.Resolve() == first.Resolve(),
"unchanged SGPR pair did not retain its native U64 value");
Check(swapped.Resolve().Instruction()->GetOpcode() ==
ValueOpcode::CompositeConstructU64 &&
mixed.Resolve().Instruction()->GetOpcode() ==
ValueOpcode::CompositeConstructU64,
"modified SGPR pair incorrectly reused an unrelated native U64 value");
}
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
void TestNativeWideValueValidation() {
using namespace ShaderRecompiler::IR;
@@ -14931,7 +14998,8 @@ int main() {
TestNewShaderRecompilerCfgIrreducibleDispatcher();
TestNewShaderRecompilerDispatcherSpillsU32x3();
TestNewShaderRecompilerPlanningOnlyLoads();
TestNewShaderRecompilerU64PairTranslation();
TestNewShaderRecompilerNativeU64Translation();
TestVertexMadU64UsesPortableProduct();
TestComputeDispatchWaveSize();
TestNewShaderRecompilerBufferLoadsGuardedByExec();
TestNewShaderRecompilerBufferAtomicsGuardedByBounds();
@@ -14956,6 +15024,7 @@ int main() {
TestValuePhiValidation();
TestWave32MaskProjection();
TestU64ShiftConstantPropagation();
TestU64RegisterPairRoundTrip();
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
TestNativeWideValueValidation();
#endif