shader: walk every resource plan with the x86 SRT walker

This commit is contained in:
nmzik committed 2026-10-24 02:57:24 +02:00
1 parent bdf48dcf3b
commit d7856753e0
12 files changed
+1782 -1701

No files matched your search

+103 -143
View File
@@ -24,13 +24,13 @@
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h"
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
#include "graphics/shader/recompiler/ir/passes/SsaRewrite.h"
#include "graphics/shader/shader.h"
#include "graphics/shader/shaderCompiler.h"
#include "libs/agc.h"
#include "spirv-tools/libspirv.hpp"
#include "xxhash.h"
#include "SrtWalkerTestMemory.h"
#include <algorithm>
#include <array>
@@ -135,8 +135,7 @@ struct TestCompileResult {
TestCompileResult RecompileForTest(
std::span<const uint32_t> code,
const ShaderRecompiler::CompileOptions &options,
ShaderRecompiler::IR::SrtMemoryReader read_memory = nullptr,
void *read_memory_data = nullptr, uint32_t push_data_start_dword = 0) {
uint32_t push_data_start_dword = 0) {
auto translated = ShaderRecompiler::TranslateProgram(code, options);
auto plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
ShaderRecompiler::IR::ResourceSnapshot resources;
@@ -144,8 +143,6 @@ TestCompileResult RecompileForTest(
const ShaderRecompiler::IR::SrtRuntime runtime{
.user_data = options.user_data,
.shader_base = reinterpret_cast<uint64_t>(code.data()),
.read_memory = read_memory,
.userdata = read_memory_data,
.read_specialization_memory = ReadHostTestMemory,
};
Check(ShaderRecompiler::IR::MaterializeResources(
@@ -1588,11 +1585,6 @@ void SetImageTestFormat(std::array<uint32_t, 64> *data, uint32_t srsrc,
(*data)[format_dword] = static_cast<uint32_t>(format) << 20u;
}
bool ReadZeroTestMemory(void *, uint64_t, std::span<uint32_t> values) {
std::ranges::fill(values, 0u);
return true;
}
constexpr uint32_t EncodeVop2(uint32_t opcode, uint32_t dst, uint32_t src0,
uint32_t src1) {
return ((opcode & 0x3fu) << 25u) | ((dst & 0xffu) << 17u) |
@@ -5408,7 +5400,8 @@ void TestNewShaderRecompilerMemoryFamilyTranslation() {
options.dump_ir = true;
options.user_data = user_data;
auto result = RecompileForTest(shader, options, ReadZeroTestMemory);
FakeGuestMemory zero_memory;
auto result = RecompileForTest(shader, options);
Check((result.decoded_dump.find("s_load_dword") != std::string::npos),
"new decoder did not decode SMEM dword load");
Check((result.decoded_dump.find("s_buffer_load_dword") != std::string::npos),
@@ -9223,7 +9216,7 @@ void TestSharedReturnPreservesDescriptorDominance() {
user_data[29] = static_cast<uint32_t>(address >> 32u);
auto options = MakeCompileOptions(ShaderType::Pixel);
options.user_data = user_data;
auto result = RecompileForTest(shader, options, ReadHostTestMemory);
auto result = RecompileForTest(shader, options);
Check(!result.program.dispatcher_fallback &&
result.resources.buffers.size() == 1u &&
result.resources.buffers[0].dwords[0] == 0x2000u,
@@ -9283,7 +9276,7 @@ void TestNestedSelectionPreservesDescriptorSources() {
auto options = MakeCompileOptions(ShaderType::Compute);
options.user_data_base = 8;
options.user_data = user_data;
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
const auto result = RecompileForTest(shader, options);
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 4u,
"nested descriptor selection lost its structured resource bindings");
for (size_t i = 0; i < result.program.info.buffers.size(); ++i) {
@@ -9359,7 +9352,7 @@ void TestSharedExitPreservesNativeDescriptorSources() {
user_data[2] = arm;
auto options = MakeCompileOptions(ShaderType::Pixel);
options.user_data = user_data;
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
const auto result = RecompileForTest(shader, options);
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 2u,
"shared-exit descriptor selection lost its structured resource bindings");
for (size_t i = 0; i < result.program.info.buffers.size(); ++i) {
@@ -9405,7 +9398,7 @@ void TestNativeScalarReadDescriptorPlanning() {
user_data[8] = mode;
auto options = MakeCompileOptions(ShaderType::Pixel);
options.user_data = user_data;
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
const auto result = RecompileForTest(shader, options);
const auto expected = mode == 2u ? table : std::array<uint32_t, 4>{};
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 1u &&
std::ranges::equal(result.resources.flattened_srt, expected),
@@ -9507,7 +9500,7 @@ void TestNativeGuardedSamplerSource(bool inline_constants) {
auto options = MakeCompileOptions(ShaderType::Pixel);
options.user_data_base = 8;
options.user_data = user_data;
const auto result = RecompileForTest(code, options, ReadHostTestMemory);
const auto result = RecompileForTest(code, options);
const auto expected = mode == 2u ? sampler : std::array<uint32_t, 4>{};
Check(!result.program.dispatcher_fallback && result.resources.samplers.size() == 1u &&
result.resources.samplers[0].dword_count == expected.size() &&
@@ -9615,8 +9608,7 @@ void TestBoundedMaterialBufferStores(bool scalar_key) {
ShaderRecompiler::IR::ResourceSnapshot snapshot;
ShaderRecompiler::IR::ResourceSpecialization specialization;
const ShaderRecompiler::IR::SrtRuntime runtime{
.user_data = user_data, .read_memory = ReadHostTestMemory,
.read_specialization_memory = ReadHostTestMemory};
.user_data = user_data, .read_specialization_memory = ReadHostTestMemory};
const auto materialize = [&] {
return ShaderRecompiler::IR::MaterializeResources(plan, runtime, snapshot, specialization);
};
@@ -9701,7 +9693,7 @@ void TestNativeDescriptorProvenanceKeepsGpuSelection() {
options.input_info.compute = &input_info;
options.user_data = user_data;
for (const auto &shader : {selected, partial, loop}) {
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
const auto result = RecompileForTest(shader, options);
Check(result.program.info.uses_dma && result.program.info.buffers.size() == 1u &&
result.program.info.buffers[0].written,
"GPU-selected scalar descriptor was materialized as a host binding");
@@ -10864,7 +10856,7 @@ void TestMemoryFedScalarLeafCall() {
struct Memory {
std::array<uint32_t, 128> words{};
uint64_t end = base + 0x10c;
bool clean = true;
bool readable = true;
} memory;
std::copy_n(std::array{0xe0040060u, 0x0010000fu, 8u, 0x5204u}.begin(),
4, memory.words.begin());
@@ -10875,13 +10867,14 @@ void TestMemoryFedScalarLeafCall() {
memory.words[66] = EncodeSop1(0x21, 125, 14);
const auto read = [](void *data, uint64_t address, std::span<uint32_t> out) {
const auto& m = *static_cast<Memory*>(data);
if (!m.clean || address < base || address > m.end || out.size_bytes() > m.end - address)
if (!m.readable || address < base || address > m.end || out.size_bytes() > m.end - address)
return false;
std::memcpy(out.data(), reinterpret_cast<const uint8_t*>(m.words.data()) + address - base,
out.size_bytes());
return true;
};
IR::SrtRuntime runtime{.read_memory = read, .userdata = &memory, .read_specialization_memory = read};
FakeGuestMemory fake(read, &memory);
IR::SrtRuntime runtime{.userdata = &memory, .read_specialization_memory = read};
const auto options = MakeCompileOptions(ShaderType::Compute);
const uint32_t gpu_callable[] = {EncodeVop1(0x02, 4, 256),
EncodeSmem0(0x09, 14, 0), 4u << 25u, EncodeSop1(0x21, 14, 14), EncodeSopp(0x01)};
@@ -10965,14 +10958,14 @@ void TestMemoryFedScalarLeafCall() {
Check(source.revision == 3 && source.reads.back().second == 8,
"shorter exact-boundary leaf was not decoded after a failed old-span read");
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
memory.clean = false;
memory.readable = false;
ExpectFatal([&] { (void)RefreshShaderSource(source, runtime); },
"unreadable call dependencies reused stale linked code");
memory.clean = true;
auto ordinary = runtime;
ordinary.read_specialization_memory = nullptr;
ExpectFatal([&] { (void)RefreshShaderSource(source, ordinary); },
"call target query accepted an ordinary non-strict memory reader");
memory.readable = true;
auto no_reader = runtime;
no_reader.read_specialization_memory = nullptr;
ExpectFatal([&] { (void)RefreshShaderSource(source, no_reader); },
"leaf code was linked without a specialization memory reader");
std::vector<uint32_t> escaping(std::begin(shader), std::end(shader) - 1);
escaping.insert(escaping.end(), {EncodeVop1(0x01, 1, 14),
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(1, 0, 0), EncodeSopp(0x01)});
@@ -10988,9 +10981,10 @@ void TestMemoryFedScalarLeafCall() {
EncodeSMovB32(15, 129), EncodeSop2(0x0f, 14, 128, 128),
EncodeSop1(0x21, 14, 14), EncodeSopp(0x01)};
auto pair_source = PrepareShaderSource(high_writer, options);
IR::SrtWalker pair_walker(pair_source.call_targets, runtime);
std::vector<uint32_t> flat;
uint32_t high = 1;
Check(pair_walker.Evaluate(pair_source.calls.front().target[1], high) && high == 0,
Check(IR::WalkSrt(pair_source.call_targets, runtime, flat, nullptr) &&
IR::WalkedValue(pair_source.call_targets, pair_source.calls.front().target[1], high) && high == 0,
"scalar pair producer used a stale preceding high-half definition");
const uint32_t joined[] = {
EncodeSopp(0x04, 2), EncodeSMovB32(14, 129), EncodeSopp(0x02, 1),
@@ -11136,7 +11130,7 @@ void TestMeshExportStorage() {
options.back_code = split ? std::span{back} : std::span<const uint32_t>{};
const auto result = RecompileForTest(
split ? std::span{front} : std::span<const uint32_t>{monolithic},
options, nullptr, nullptr, push_data_start);
options, push_data_start);
CheckSpirvBinaryValidates(result.spirv);
const auto &layout = result.program.bindings;
Check(layout.UsesPushData() == (push_data_start == PushData::MeshDrawDwordCount) &&
@@ -11284,13 +11278,11 @@ void TestMergedShaderUserDataSnapshot() {
const auto plan = IR::ExtractResourcePlan(program);
for (const auto *params : {&first, &second}) {
const auto user_data = std::span(params->user_data).first(params->user_data_count);
const IR::SrtRuntime runtime{.user_data = user_data,
.read_memory = ReadHostTestMemory};
IR::SrtWalker walker(plan, runtime);
const IR::SrtRuntime runtime{.user_data = user_data};
IR::DescriptorValue front_descriptor, back_descriptor;
const auto &table = params == &first ? first_table : second_table;
Check(walker.EvaluateDescriptor(program.info.buffers[0].source, front_descriptor) &&
walker.EvaluateDescriptor(program.info.buffers[1].source, back_descriptor) &&
Check(EvaluateSrtDescriptor(plan, runtime, program.info.buffers[0].source, front_descriptor) &&
EvaluateSrtDescriptor(plan, runtime, program.info.buffers[1].source, back_descriptor) &&
std::equal(user_data.begin() + 8, user_data.end(),
front_descriptor.dwords.begin()) &&
std::equal(table.begin(), table.end(), back_descriptor.dwords.begin()),
@@ -13824,7 +13816,7 @@ void TestAutomaticObjectPrimitiveId() {
PushData::GraphicsDrawDwords(true, false) == PushData::MeshDrawDwordCount,
"graphics draw data reserved an incorrect prefix");
for (const uint32_t start : {prefix, PushData::DwordCount}) {
const auto result = RecompileForTest(shader, options, nullptr, nullptr, start);
const auto result = RecompileForTest(shader, options, start);
Check(result.program.bindings.UsesPushData() == (start == prefix),
"object ID changed shader-data push/spill selection");
Check(std::ranges::any_of(result.program.info.outputs, [](const StageOutput& output) {
@@ -13841,7 +13833,7 @@ void TestAutomaticObjectPrimitiveId() {
}
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
pixel.target_shader_mask = 0xf0000000u;
ExpectFatal([&] { (void)RecompileForTest(shader, options, nullptr, nullptr, prefix); },
ExpectFatal([&] { (void)RecompileForTest(shader, options, prefix); },
"automatic RT7 silently overlapped an explicit MRT7 export");
#endif
}
@@ -14489,33 +14481,6 @@ void CheckFlattenedReadSlots(const ShaderRecompiler::IR::Program &program,
}
}
bool ReadSrtHostDword(void *, uint64_t address, std::span<uint32_t> values) {
if (address == 0 || values.empty()) {
return false;
}
std::memcpy(values.data(), reinterpret_cast<const void *>(address), values.size_bytes());
return true;
}
struct SrtHostRange {
const uint32_t *data;
size_t count;
};
bool ReadSrtHostRangeDword(void *userdata, uint64_t address, std::span<uint32_t> values) {
const auto *range = static_cast<const SrtHostRange *>(userdata);
if (range == nullptr || range->data == nullptr || range->count == 0 || values.empty()) {
return false;
}
const auto base = reinterpret_cast<uint64_t>(range->data);
const auto size = range->count * sizeof(uint32_t);
if (address < base || values.size_bytes() > size || address - base > size - values.size_bytes()) {
return false;
}
std::memcpy(values.data(), reinterpret_cast<const void *>(address), values.size_bytes());
return true;
}
void TestTypedDescriptorRealWideMoveTranslation() {
const uint32_t shader[] = {
EncodeSop1(0x04, 0, 20), // s_mov_b64 s[0:1], s[20:21]
@@ -14581,7 +14546,7 @@ void TestComputeImageFill() {
options.input_info.compute = &compute;
options.user_data = userdata;
enum class Mutation { None, Offset, WrongAxis, VaryingValue, Predicate, ExtraStore, A16 };
const auto Run = [&](Mutation mutation, bool clean = true) {
const auto Run = [&](Mutation mutation) {
auto translated = ShaderRecompiler::TranslateProgram(shader, options);
auto &program = translated.program;
for (auto *block : program.blocks) {
@@ -14618,13 +14583,11 @@ void TestComputeImageFill() {
auto plan = ExtractResourcePlan(program);
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
Check(MaterializeResources(plan,
{.user_data = userdata, .read_memory = ReadHostTestMemory,
.read_specialization_memory = clean ? ReadHostTestMemory : nullptr},
snapshot, specialization),
// The fill value is a walker load, so it needs no specialization memory reader.
Check(MaterializeResources(plan, {.user_data = userdata}, snapshot, specialization),
"captured stencil clear did not materialize");
const auto &fill = snapshot.uniform_fill;
const bool expected = mutation == Mutation::None && clean;
const bool expected = mutation == Mutation::None;
Check((fill.kind == UniformFillKind::Image) == expected,
"image fill proof accepted an unsafe store or missed the captured clear");
if (expected) {
@@ -14634,7 +14597,6 @@ void TestComputeImageFill() {
}
};
Run(Mutation::None);
Run(Mutation::None, false);
for (const auto mutation : {Mutation::Offset, Mutation::WrongAxis, Mutation::VaryingValue,
Mutation::Predicate, Mutation::ExtraStore, Mutation::A16})
Run(mutation);
@@ -14835,27 +14797,22 @@ void TestGpuProducedWritableDescriptor() {
"native GPU-built writable descriptor acquired an unnecessary alias proof");
struct Reads {
std::array<uint32_t, 3> header{0x26c0u, 0u, 151u};
uint32_t ordinary = 0;
uint32_t strict = 0;
uint32_t count = 0;
} reads;
const SrtRuntime runtime{
.user_data = user_data,
.read_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
if (words.size() != 1) return false;
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<uint32_t> words) {
auto &reads = *static_cast<Reads *>(data);
const uint32_t index = address == 0x300315028ull ? 0u
: address == 0x30031502cull ? 1u
: address == 0x300315074ull ? 2u : 3u;
if (index == 3u) return false;
++reads.ordinary;
uint32_t index = 0;
switch (address) {
case 0x300315028ull: index = 0u; break;
case 0x30031502cull: index = 1u; break;
case 0x300315074ull: index = 2u; break;
default: return false;
}
++reads.count;
words[0] = reads.header[index];
return true;
},
.userdata = &reads,
.read_specialization_memory = +[](void *data, uint64_t, std::span<uint32_t>) {
++static_cast<Reads *>(data)->strict;
return false; // Current GPU bytes are available only through the ordinary reader.
}};
}, &reads);
const SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
const auto check = [&](uint32_t low, uint32_t high, uint32_t count, uint32_t reads_expected) {
@@ -14866,8 +14823,7 @@ void TestGpuProducedWritableDescriptor() {
snapshot.buffers[0].dwords[1] == high &&
snapshot.buffers[0].dwords[2] == count &&
snapshot.buffers[0].dwords[3] == 0x16204u &&
snapshot.specialization_reads.empty() && reads.strict == 0 &&
reads.ordinary == reads_expected,
snapshot.specialization_reads.empty() && reads.count == reads_expected,
"GPU-produced descriptor lost current scalar data or repeated its reads");
};
check(0x003176c0u, 0x00800003u, 151u, 3u);
@@ -14929,25 +14885,29 @@ void TestUniformSelectedWritableDescriptor() {
auto plan = ExtractResourcePlan(translated.program);
Check(plan.info.buffers.size() == 1 && plan.info.buffers[0].written,
"uniform address selection lost its writable buffer");
const auto read = +[](void *data, uint64_t address, std::span<uint32_t> words) {
const bool alternate = *static_cast<uint32_t *>(data) == 0;
if (words.size() != 1) return false;
// An unavailable read would complete as zero, so the reader counts them.
struct Memory { const uint32_t *flag; uint32_t unavailable = 0; } memory{&user_data[2]};
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<uint32_t> words) {
auto &memory = *static_cast<Memory *>(data);
const bool alternate = *memory.flag == 0;
bool available = false;
switch (address) {
case 0x1040: words[0] = 8u; return alternate;
case 0x1044: words[0] = 1u; return alternate;
case 0x1080: words[0] = 3u; return !alternate;
case 0x109c: words[0] = 2u; return !alternate;
case 0x1084: words[0] = 10u; return true;
default: return false;
case 0x1040: words[0] = 8u; available = alternate; break;
case 0x1044: words[0] = 1u; available = alternate; break;
case 0x1080: words[0] = 3u; available = !alternate; break;
case 0x109c: words[0] = 2u; available = !alternate; break;
case 0x1084: words[0] = 10u; available = true; break;
default: break;
}
};
const SrtRuntime runtime{.user_data = user_data, .read_memory = read,
.userdata = &user_data[2], .read_specialization_memory = read};
memory.unavailable += !available;
return available;
}, &memory);
const SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
for (const uint32_t flag : {1u, 0u, 1u}) {
user_data[2] = flag;
Check(MaterializeResources(plan, runtime, snapshot, specialization),
Check(MaterializeResources(plan, runtime, snapshot, specialization) && memory.unavailable == 0,
"uniform selection evaluated an unavailable untaken scalar source");
const auto &descriptor = snapshot.buffers[0].dwords;
Check(descriptor[0] == (flag ? 0xe0u : 4u) &&
@@ -14977,18 +14937,16 @@ void TestUniformSelectedDescriptorLoadAddress() {
Check(plan.srt_reads.size() == 4u && plan.info.buffers.size() == 1u &&
plan.info.buffers[0].written && !plan.capture_specialization_reads,
"conditional scalar address duplicated or detached descriptor reads");
const auto read = +[](void *data, uint64_t address, std::span<uint32_t> words) {
FakeGuestMemory fake(+[](void *data, uint64_t address, std::span<uint32_t> words) {
const auto flag = *static_cast<uint32_t *>(data);
const uint64_t base = flag ? 0x2000u : 0x1000u;
const std::array<uint32_t, 4> descriptor{0x3000u + flag * 0x1000u,
4u << 16u, 16u, 0x16204u};
if (words.size() != 1u || address < base || address >= base + 16u ||
(address & 3u) != 0u) return false;
if (address < base || address >= base + 16u || (address & 3u) != 0u) return false;
words[0] = descriptor[(address - base) / 4u];
return true;
};
const SrtRuntime runtime{.user_data = user_data, .read_memory = read,
.userdata = &user_data[0]};
}, &user_data[0]);
const SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
for (const uint32_t flag : {0u, 1u, 0u}) {
@@ -15058,7 +15016,7 @@ void TestBoundedScalarMaterialImageKeys() {
uint32_t stride = 160;
uint32_t records = 409;
uint64_t unavailable = 0;
bool clean = true;
bool readable = true;
bool mixed = false;
bool same_key = false;
uint32_t key_reads = 0;
@@ -15066,7 +15024,7 @@ void TestBoundedScalarMaterialImageKeys() {
} memory;
const auto read = +[](void *data, uint64_t address, std::span<uint32_t> words) {
auto &m = *static_cast<Memory *>(data);
if (!m.clean || (m.unavailable >= address &&
if (!m.readable || (m.unavailable >= address &&
m.unavailable - address < words.size_bytes())) return false;
if (words.size() == 1 && address >= 0x10a0u && address < 0x10c0u) {
const uint32_t descriptors[] = {0x2000u, m.stride << 16u, m.records, 0x5204u,
@@ -15101,6 +15059,7 @@ void TestBoundedScalarMaterialImageKeys() {
}
return false;
};
FakeGuestMemory fake(read, &memory);
const SrtRuntime runtime{.user_data = user_data, .userdata = &memory,
.read_specialization_memory = read};
ResourceSnapshot snapshot;
@@ -15138,9 +15097,11 @@ void TestBoundedScalarMaterialImageKeys() {
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
memory.key_reads == 0 && memory.images == 0 && snapshot.images.size() == 2,
"empty scalar material loop retained image probes");
memory = {.clean = false};
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
"dirty scalar material proof was accepted");
// Unreadable descriptor and count reads are zero, which leaves the loop empty.
memory = {.readable = false};
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
memory.key_reads == 0 && memory.images == 0 && snapshot.images.size() == 2,
"unreadable scalar material count did not read as zero");
for (const uint64_t unavailable : {0x2094u, 0x211cu}) {
memory = {.unavailable = unavailable};
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
@@ -15218,12 +15179,14 @@ void TestTypedDescriptorRealCarryAndScalarLoads() {
std::array<uint32_t, 64> carry_user_data{};
const uint64_t shader_base = 0x00000012fffffffbull;
const uint64_t expected_pc = shader_base + 5u;
ShaderRecompiler::IR::SrtRuntime carry_runtime{carry_user_data, shader_base,
nullptr, nullptr};
ShaderRecompiler::IR::DescriptorValue carry_value;
Check(ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(carry_ir), carry_runtime).EvaluateDescriptor(carry_source_index, carry_value) &&
carry_value.dwords[0] == static_cast<uint32_t>(expected_pc) &&
carry_value.dwords[1] == static_cast<uint32_t>(expected_pc >> 32u),
const ShaderRecompiler::IR::SrtRuntime carry_runtime{carry_user_data, shader_base};
auto carry_plan = ShaderRecompiler::IR::ExtractResourcePlan(carry_ir);
const auto &carry_words = carry_plan.descriptor_sources[carry_source_index].dwords;
uint32_t carry_low = 0, carry_high = 0;
Check(EvaluateSrtValue(carry_plan, carry_runtime, carry_words[0], carry_low) &&
EvaluateSrtValue(carry_plan, carry_runtime, carry_words[1], carry_high) &&
carry_low == static_cast<uint32_t>(expected_pc) &&
carry_high == static_cast<uint32_t>(expected_pc >> 32u),
"S_GETPC shader-base or add/addc carry evaluation was incorrect");
const uint32_t load_shader[] = {
@@ -15285,19 +15248,21 @@ void TestTypedDescriptorRealCarryAndScalarLoads() {
BuildTypedPlan(inline_sampler_shader,
static_cast<uint32_t>(std::size(inline_sampler_shader)),
inline_sampler_ir);
// The walk also reads the sampled image's user-data descriptor.
auto user_data = ImageTestUserData();
ShaderRecompiler::IR::DescriptorValue sampler;
ShaderRecompiler::IR::SrtRuntime runtime;
const ShaderRecompiler::IR::SrtRuntime runtime{user_data};
Check(inline_sampler_ir.info.samplers.size() == 1 &&
TypedDescriptorSource(inline_sampler_ir,
inline_sampler_ir.info.samplers[0].source) !=
nullptr &&
ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(inline_sampler_ir), runtime).EvaluateDescriptor(inline_sampler_ir.info.samplers[0].source, sampler) &&
EvaluateSrtDescriptor(ShaderRecompiler::IR::ExtractResourcePlan(inline_sampler_ir), runtime,
inline_sampler_ir.info.samplers[0].source, sampler) &&
sampler.dwords[0] == 0 && sampler.dwords[1] == 0x00fff000u &&
sampler.dwords[2] == 0x09500000u && sampler.dwords[3] == 0,
"real inline sampler construction was unresolved or evaluated "
"incorrectly");
auto user_data = ImageTestUserData();
auto options = MakeCompileOptions(ShaderType::Pixel);
options.user_data = user_data;
auto result = RecompileForTest(inline_sampler_shader, options);
@@ -15325,9 +15290,8 @@ void TestSrtWalkerRealSmemTranslation() {
user_data[8] = static_cast<uint32_t>(address);
user_data[9] = static_cast<uint32_t>(address >> 32u);
std::vector<uint32_t> flat;
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
nullptr};
const auto walked = ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat);
const ShaderRecompiler::IR::SrtRuntime runtime{user_data};
const auto walked = ShaderRecompiler::IR::WalkSrt(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime, flat, nullptr);
Check(walked, "SRT walk failed");
Check(flat.size() == table.size() &&
std::equal(flat.begin(), flat.end(), table.begin()),
@@ -15353,10 +15317,9 @@ void TestSrtWalkerVccBaseTranslation() {
const auto address = reinterpret_cast<uint64_t>(table.data());
user_data[27] = static_cast<uint32_t>(address);
user_data[28] = static_cast<uint32_t>(address >> 32u);
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
nullptr};
const ShaderRecompiler::IR::SrtRuntime runtime{user_data};
std::vector<uint32_t> flat;
Check(ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat), "SRT walk failed");
Check(ShaderRecompiler::IR::WalkSrt(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime, flat, nullptr), "SRT walk failed");
Check(flat.size() == table.size() &&
std::equal(flat.begin(), flat.end(), table.begin()),
"typed SSA lost an SMEM base copied through VCC");
@@ -15382,17 +15345,16 @@ void TestSrtWalkerRealSBufferTranslation() {
user_data[9] = static_cast<uint32_t>(address >> 32u);
user_data[10] = sizeof(table);
std::vector<uint32_t> flat;
const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword,
nullptr};
const ShaderRecompiler::IR::SrtRuntime runtime{user_data};
const auto plan = ShaderRecompiler::IR::ExtractResourcePlan(ir);
const auto walked = ShaderRecompiler::IR::SrtWalker(plan, runtime).RefreshFlatBuffer(flat);
const auto walked = ShaderRecompiler::IR::WalkSrt(plan, runtime, flat, nullptr);
Check(walked, "SRT walk failed");
Check(flat.size() == 4 &&
std::equal(flat.begin(), flat.end(), table.begin()),
"real S_BUFFER_LOAD walk did not align offset components independently");
user_data[10] = 3 * sizeof(uint32_t);
const auto bounds_walked = ShaderRecompiler::IR::SrtWalker(plan, runtime).RefreshFlatBuffer(flat);
const auto bounds_walked = ShaderRecompiler::IR::WalkSrt(plan, runtime, flat, nullptr);
Check(bounds_walked && flat == std::vector<uint32_t>{table[0], table[1], table[2], 0u},
"real S_BUFFER_LOAD walk did not zero only the out-of-bounds DWORD");
CheckFlattenedReadSlots(
@@ -15408,7 +15370,7 @@ void TestSrtWalkerRealSBufferTranslation() {
static_cast<uint32_t>(std::size(negative_shader)),
negative_ir);
user_data[10] = sizeof(table);
Check(!ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(negative_ir), runtime).RefreshFlatBuffer(flat),
Check(!ShaderRecompiler::IR::WalkSrt(ShaderRecompiler::IR::ExtractResourcePlan(negative_ir), runtime, flat, nullptr),
"real S_BUFFER_LOAD walk accepted a negative immediate");
}
@@ -15443,11 +15405,9 @@ void TestScalarMemorySourcesCapturedBeforeWrites() {
if (opcode == 0x0a) {
user_data[base + 2u] = sizeof(table);
}
SrtHostRange range{table.data(), table.size()};
const ShaderRecompiler::IR::SrtRuntime runtime{
user_data, 0, ReadSrtHostRangeDword, &range};
const ShaderRecompiler::IR::SrtRuntime runtime{user_data};
std::vector<uint32_t> flat;
Check(ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat), "SRT walk failed");
Check(ShaderRecompiler::IR::WalkSrt(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime, flat, nullptr), "SRT walk failed");
Check(flat.size() == table.size() &&
std::equal(flat.begin(), flat.end(), table.begin()),
"overlapping scalar-memory load did not capture its sources before "
@@ -16007,7 +15967,7 @@ void TestGraphicsPushConstantPlacement() {
constexpr uint32_t PushDataStart = 7;
auto placed =
RecompileForTest(shader, options, nullptr, nullptr, PushDataStart);
RecompileForTest(shader, options, PushDataStart);
Check(placed.program.bindings.UsesPushData() &&
placed.program.bindings.push_data_start_dword == PushDataStart &&
placed.program.bindings.descriptor_counts[static_cast<size_t>(BindingKind::ShaderData)] == 0,
@@ -16016,7 +15976,7 @@ void TestGraphicsPushConstantPlacement() {
"SPIR-V push-data block did not use the canonical zero base");
CheckSpirvBinaryValidates(placed.spirv);
auto exact = RecompileForTest(shader, options, nullptr, nullptr, 28);
auto exact = RecompileForTest(shader, options, 28);
Check(exact.program.bindings.UsesPushData() &&
exact.program.bindings.push_data_start_dword == 28 &&
exact.program.bindings.ShaderDataDwords() == 4,
@@ -16026,8 +15986,8 @@ void TestGraphicsPushConstantPlacement() {
Check(cursor == 32, "exact-fit shader data advanced the cursor incorrectly");
CheckSpirvBinaryValidates(exact.spirv);
auto spill = RecompileForTest(shader, options, nullptr, nullptr, 29);
auto full = RecompileForTest(shader, options, nullptr, nullptr, 32);
auto spill = RecompileForTest(shader, options, 29);
auto full = RecompileForTest(shader, options, 32);
Check(!spill.program.bindings.UsesPushData() &&
spill.program.bindings.descriptor_counts[static_cast<size_t>(BindingKind::ShaderData)] == 1 &&
spill.program.bindings == full.program.bindings && spill.spirv == full.spirv,