diff --git a/CMakeLists.txt b/CMakeLists.txt index ac75a8dc..c8f7700e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -393,8 +393,10 @@ add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.c add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/ScalarProvenanceTests.cpp" + "${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp" "${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp" "${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ReadLaneElimination.cpp" + "${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp" "${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp" ) target_link_libraries(scalar_provenance_tests fmt::fmt) diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index cdde351c..3b955678 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -598,7 +598,6 @@ struct ResourcePlan { std::vector descriptor_sources; std::vector control_flow; std::vector srt_reads; - std::vector clean_flat_slots; bool requires_specialization_memory = false; bool capture_specialization_reads = false; bool srt_plan_complete = false; diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index d50f32de..468f4e26 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -173,32 +173,28 @@ const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) { return &program.descriptor_sources[source]; } -void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* source, - std::span slots, Value extra = {}) { +void MarkCleanReads(const std::unordered_set& planned_reads, + const DescriptorSource* source, Value extra = {}) { if (source == nullptr && extra.IsEmpty()) { return; } - std::vector pending; + std::vector pending; if (source != nullptr) { pending.assign(source->dwords.begin(), source->dwords.begin() + source->dword_count); } if (!extra.IsEmpty()) pending.push_back(extra); std::vector visited; while (!pending.empty()) { - auto value = pending.back().Resolve(); + auto* inst = pending.back().Resolve().TryInstruction(); pending.pop_back(); - const auto* inst = value.TryInstruction(); if (inst == nullptr || std::ranges::find(visited, inst) != visited.end()) { continue; } visited.push_back(inst); - if (inst->GetOpcode() == ValueOpcode::ReadConst) { - const auto slot = inst->Arg(1).Resolve(); - if (slot.IsImmediate() && slot.GetType() == Type::U32 && slot.U32() < slots.size()) { - slots[slot.U32()] = 1u; - pending.push_back(program.srt_reads[slot.U32()].value); - } - continue; + if (planned_reads.contains(inst)) { + auto flags = inst->Flags(); + flags.clean = 1u; + inst->SetFlags(flags); } for (size_t arg = 0; arg < inst->NumArgs(); arg++) { pending.push_back(inst->Arg(arg)); @@ -945,6 +941,12 @@ ResourcePlan ExtractResourcePlan(const Program& program) { if (source == nullptr) { return value; } + if (source->GetOpcode() == ValueOpcode::ReadConst) { + const auto slot = source->Arg(1).Resolve(); + EXIT_IF(!slot.IsImmediate() || slot.GetType() != Type::U32 || + slot.U32() >= program.srt_reads.size()); + return Clone(program.srt_reads[slot.U32()].value); + } if (source->GetOpcode() == ValueOpcode::ReadFirstLane && ValidateRuntimeValue(program, source->Arg(0))) { // Uniform values need no new EXEC context; preserve their shared evaluation memo. @@ -965,6 +967,11 @@ ResourcePlan ExtractResourcePlan(const Program& program) { } auto& target = plan.value_storage.emplace_back(source->GetOpcode(), source->Flags()); + if (source->GetOpcode() == ValueOpcode::LoadAddressU32 || + source->GetOpcode() == ValueOpcode::ReadConstBuffer || + source->GetOpcode() == ValueOpcode::LoadBufferU32) { + target.SetFlags(SrtReadFlags {.index = source->Flags().index}); + } cloned.emplace(source, &target); if (source->GetOpcode() == ValueOpcode::Phi) { for (size_t index = 0; index < source->NumArgs(); index++) { @@ -978,12 +985,15 @@ ResourcePlan ExtractResourcePlan(const Program& program) { return Value(&target); }; + bool capture_indirect_reads = false; plan.descriptor_sources.reserve(program.descriptor_sources.size()); for (const auto& source: program.descriptor_sources) { auto& target = plan.descriptor_sources.emplace_back(); target.dword_count = source.dword_count; target.indirect_descriptor = source.indirect_descriptor; if (target.indirect_descriptor.has_value()) { + plan.requires_specialization_memory = true; + capture_indirect_reads = true; target.indirect_descriptor->key_count = Clone(target.indirect_descriptor->key_count); target.indirect_descriptor->selector_first = Clone(target.indirect_descriptor->selector_first); @@ -1034,34 +1044,39 @@ ResourcePlan ExtractResourcePlan(const Program& program) { for (uint32_t i = 0; i < plan.uniform_fill.fill.words; ++i) { plan.uniform_fill.values[i] = Clone(plan.uniform_fill.values[i]); } - plan.clean_flat_slots.resize(plan.srt_reads.size()); - bool capture_indirect_reads = false; - for (const auto& source: plan.descriptor_sources) { - if (!source.indirect_descriptor.has_value()) continue; - const auto& indirect = *source.indirect_descriptor; - plan.requires_specialization_memory = true; - capture_indirect_reads = true; - if (indirect.selector) - MarkCleanFlatSlots(plan, Source(plan, indirect.selector->source), plan.clean_flat_slots); - MarkCleanFlatSlots(plan, nullptr, plan.clean_flat_slots, indirect.selector_mask); - MarkCleanFlatSlots(plan, nullptr, plan.clean_flat_slots, indirect.selector_first); - MarkCleanFlatSlots(plan, nullptr, plan.clean_flat_slots, indirect.key_count); - if (indirect.sources.empty()) { - MarkCleanFlatSlots(plan, Source(plan, indirect.table_source), plan.clean_flat_slots); - } else { - for (const auto candidate: indirect.sources) { - MarkCleanFlatSlots(plan, Source(plan, candidate), plan.clean_flat_slots); - } - } - } plan.capture_specialization_reads |= capture_indirect_reads || !plan.control_flow.empty(); if (plan.capture_specialization_reads) { + // Every former flat alias now targets its retained raw read. Mark those reads + // on the normalized graph, so discarded Phi/EXEC dependencies stay discarded. + std::unordered_set planned_reads; + for (const auto& read: plan.srt_reads) { + auto* inst = read.value.ResolveInstruction(); + EXIT_IF(inst == nullptr || (inst->GetOpcode() != ValueOpcode::LoadAddressU32 && + inst->GetOpcode() != ValueOpcode::ReadConstBuffer)); + planned_reads.insert(inst); + } + for (const auto& source: plan.descriptor_sources) { + if (!source.indirect_descriptor.has_value()) continue; + const auto& indirect = *source.indirect_descriptor; + if (indirect.selector) + MarkCleanReads(planned_reads, Source(plan, indirect.selector->source)); + MarkCleanReads(planned_reads, nullptr, indirect.selector_mask); + MarkCleanReads(planned_reads, nullptr, indirect.selector_first); + MarkCleanReads(planned_reads, nullptr, indirect.key_count); + if (indirect.sources.empty()) { + MarkCleanReads(planned_reads, Source(plan, indirect.table_source)); + } else { + for (const auto candidate: indirect.sources) { + MarkCleanReads(planned_reads, Source(plan, candidate)); + } + } + } // Clean writable addresses prove that shader writes cannot overlap captured reads. for (const auto& buffer: plan.info.buffers) { - if (buffer.written) MarkCleanFlatSlots(plan, Source(plan, buffer.source), plan.clean_flat_slots); + if (buffer.written) MarkCleanReads(planned_reads, Source(plan, buffer.source)); } for (const auto& image: plan.info.images) { - if (image.written) MarkCleanFlatSlots(plan, Source(plan, image.source), plan.clean_flat_slots); + if (image.written) MarkCleanReads(planned_reads, Source(plan, image.source)); } } if (capture_indirect_reads) plan.resource_tracking_complete &= !program.has_address_writes; @@ -1086,7 +1101,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime observed.read_memory = CaptureOrdinaryRead; } SrtWalker clean(program, CleanRuntime(observed)); - SrtWalker walker(program, observed, program.clean_flat_slots, + SrtWalker walker(program, observed, capture_reads || program.requires_specialization_memory ? &clean : nullptr); if (!walker.RefreshFlatBuffer(snapshot.flattened_srt)) { return false; diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp index 610d49eb..12ecdfc9 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp @@ -324,10 +324,9 @@ private: } // namespace SrtWalker::SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime, - std::span clean_flat_slots, SrtWalker* clean_evaluator, - Value active_mask) - : m_program(program), m_runtime(runtime), m_clean_flat_slots(clean_flat_slots), - m_clean_evaluator(clean_evaluator), m_active_mask(active_mask.Resolve()), + SrtWalker* clean_evaluator, Value active_mask) + : m_program(program), m_runtime(runtime), m_clean_evaluator(clean_evaluator), + m_active_mask(active_mask.Resolve()), m_context(AcquireContext(program)) {} SrtWalker::~SrtWalker() { --m_program.evaluation_depth; } @@ -455,7 +454,7 @@ bool SrtWalker::EvaluateExtract(const Inst& inst, uint64_t& result) { } bool SrtWalker::EvaluateRawRead(const Inst& inst, uint64_t& result) { - const auto flags = inst.Flags(); + const auto flags = inst.Flags(); if (flags.index >= m_program.memory_info.size()) { return false; } @@ -557,9 +556,8 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) { return EvaluateWide(inst.Arg(0), result); } const auto clean_runtime = CleanRuntime(m_runtime); - SrtWalker clean_active(m_program, clean_runtime, {}, nullptr, inst.Arg(1)); - SrtWalker active(m_program, m_runtime, m_clean_flat_slots, &clean_active, - inst.Arg(1)); + SrtWalker clean_active(m_program, clean_runtime, nullptr, inst.Arg(1)); + SrtWalker active(m_program, m_runtime, &clean_active, inst.Arg(1)); return active.EvaluateWide(inst.Arg(0), result); } case ValueOpcode::BitCastU32F32: @@ -573,25 +571,14 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) { result = static_cast(a) | (static_cast(static_cast(b)) << 32u); return true; - case ValueOpcode::ReadConst: { - const auto slot = inst.Arg(1).Resolve(); - if (!slot.IsImmediate() || slot.GetType() != Type::U32 || - slot.U32() >= m_program.srt_reads.size()) { - return false; - } - if (slot.U32() < m_clean_flat_slots.size() && - m_clean_flat_slots[slot.U32()] != 0u && m_clean_evaluator != nullptr) { - return m_clean_evaluator->EvaluateWide(m_program.srt_reads[slot.U32()].value, - result); - } - return EvaluateWide(m_program.srt_reads[slot.U32()].value, result); - } case ValueOpcode::LoadAddressU32: case ValueOpcode::ReadConstBuffer: case ValueOpcode::LoadBufferU32: if (IsRawRead(m_program, inst)) { - if (inst.GetOpcode() == ValueOpcode::LoadBufferU32 && m_clean_evaluator != nullptr && - m_clean_evaluator->m_active_mask == m_active_mask) { + if (m_clean_evaluator != nullptr && + (inst.Flags().clean != 0u || + (inst.GetOpcode() == ValueOpcode::LoadBufferU32 && + m_clean_evaluator->m_active_mask == m_active_mask))) { return m_clean_evaluator->EvaluateWide(Value(const_cast(&inst)), result); } return EvaluateRawRead(inst, result); @@ -934,8 +921,8 @@ bool SrtWalker::RefreshFlatBuffer(std::vector& flat) { const auto refresh = [&](uint32_t slot) { if (slot >= m_program.srt_reads.size()) return false; const auto& read = m_program.srt_reads[slot]; - const bool clean = read.flat_offset < m_clean_flat_slots.size() && - m_clean_flat_slots[read.flat_offset] != 0u; + const auto* inst = read.value.ResolveInstruction(); + const bool clean = inst != nullptr && inst->Flags().clean != 0u; if (clean && (m_clean_evaluator == nullptr || m_runtime.read_specialization_memory == nullptr)) return false; auto& evaluator = clean ? *m_clean_evaluator : *this; diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h index 50cb67bc..32a057c6 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h @@ -20,6 +20,13 @@ struct SrtRuntime { std::span workgroup_counts; }; +// Retained reads no longer need the guest instruction PC. A clean read evaluates +// its address and bounds through the strict reader as well as the final DWORD. +struct SrtReadFlags { + uint32_t index = 0; + uint32_t clean = 0; +}; + enum class RuntimeValueType { Any, Integer }; bool ValidateRuntimeValue(const ResourcePlan& program, Value value, @@ -31,8 +38,7 @@ SrtRuntime CleanRuntime(SrtRuntime runtime); class SrtWalker { public: SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime, - std::span clean_flat_slots = {}, SrtWalker* clean_evaluator = nullptr, - Value active_mask = {}); + SrtWalker* clean_evaluator = nullptr, Value active_mask = {}); ~SrtWalker(); SrtWalker(const SrtWalker&) = delete; SrtWalker& operator=(const SrtWalker&) = delete; @@ -54,7 +60,6 @@ private: const ResourcePlan& m_program; SrtRuntime m_runtime; - std::span m_clean_flat_slots; SrtWalker* m_clean_evaluator = nullptr; Value m_active_mask; ResourcePlan::EvaluationContext& m_context; diff --git a/tests/ResourceMaterializationTests.cpp b/tests/ResourceMaterializationTests.cpp index b144769c..bb38eb45 100644 --- a/tests/ResourceMaterializationTests.cpp +++ b/tests/ResourceMaterializationTests.cpp @@ -32,7 +32,7 @@ AddValueBlock(Libs::Graphics::ShaderRecompiler::IR::Program &program) { return *result; } -Libs::Graphics::ShaderRecompiler::IR::ResourcePlan SrtPlan(uint64_t address) { +Libs::Graphics::ShaderRecompiler::IR::Program SrtProgram(uint64_t address) { using namespace Libs::Graphics::ShaderRecompiler::IR; Program program; program.stage = Libs::Graphics::ShaderType::Compute; @@ -62,7 +62,7 @@ Libs::Graphics::ShaderRecompiler::IR::ResourcePlan SrtPlan(uint64_t address) { source.dwords[1] = Value(0u); source.dword_count = 2; program.descriptor_sources.push_back(source); - return ExtractResourcePlan(program); + return program; } Libs::Graphics::ShaderRecompiler::IR::ResourcePlan UnbasedFlatPlan() { @@ -152,7 +152,7 @@ Libs::Graphics::ShaderRecompiler::IR::Program MixedSamplerProgram() { void TestMappedSrtUsesDirectReaderByDefault() { using namespace Libs::Graphics::ShaderRecompiler::IR; const uint32_t dword = 0x12345678; - auto plan = SrtPlan(reinterpret_cast(&dword)); + auto plan = ExtractResourcePlan(SrtProgram(reinterpret_cast(&dword))); uint32_t specialization_reads = 0; const SrtRuntime runtime{.userdata = &specialization_reads, .read_specialization_memory = @@ -170,7 +170,7 @@ void TestMappedSrtUsesDirectReaderByDefault() { void TestIntegerRuntimeValueFollowsSrtReads() { using namespace Libs::Graphics::ShaderRecompiler::IR; - auto plan = SrtPlan(0x10000); + auto plan = SrtProgram(0x10000); const auto root = plan.descriptor_sources.front().dwords[0]; Check(ValidateRuntimeValue(plan, root, RuntimeValueType::Integer), "integer SRT read was rejected"); @@ -217,6 +217,98 @@ void TestIntegerRuntimeValueFollowsSrtReads() { "cyclic SRT read-first-lane dependency was accepted"); } +void TestSrtAliasesRetainReadPolicy() { + using namespace Libs::Graphics::ShaderRecompiler::IR; + auto program = SrtProgram(0x1000u); + auto &block = *program.blocks[0]; + const auto append_read = [&](uint32_t address) { + auto &handle = block.AppendNewInst(ValueOpcode::GetAddressResource, + {Value(address), Value(0u)}); + auto &read = block.AppendNewInst(ValueOpcode::LoadAddressU32, + {Value(&handle), Value(0u), Value(0u), Value(true)}); + // Different reads deliberately share MemoryInfo but have different guest PCs. + read.SetFlags(MemoryFlags{.index = 0, .pc = address}); + const auto slot = static_cast(program.srt_reads.size()); + program.srt_reads.push_back({Value(&read), slot}); + auto &srt = block.AppendNewInst(ValueOpcode::GetSrtResource); + return Value(&block.AppendNewInst(ValueOpcode::ReadConst, + {Value(&srt), Value(slot)})); + }; + const auto ordinary = append_read(0x2000u); + const auto pointer = append_read(0x3000u); + program.srt_reads[0].value.Instruction()->Arg(0).Instruction()->SetArg(0, pointer); + auto &mask = block.AppendNewInst(ValueOpcode::INotEqual32, {ordinary, Value(0u)}); + auto &first = block.AppendNewInst(ValueOpcode::ReadFirstLane, + {Value(9u), Value(&mask)}); + program.descriptor_sources.push_back({.dwords = {Value(&first), Value(0u)}, + .dword_count = 2}); + DescriptorSource indirect; + indirect.indirect_descriptor.emplace().sources = {0, 1}; + program.descriptor_sources.push_back(indirect); + auto plan = ExtractResourcePlan(program); + for (const auto &inst : plan.value_storage) { + Check(inst.GetOpcode() != ValueOpcode::ReadConst && + inst.GetOpcode() != ValueOpcode::GetSrtResource, + "retained plan still contains flattened-read aliases"); + } + Check(plan.descriptor_sources[0].dwords[0] == plan.srt_reads[0].value && + plan.srt_reads[0].value.Instruction()->Flags().clean == 1u && + plan.srt_reads[1].value.Instruction()->Flags().clean == 0u && + plan.srt_reads[2].value.Instruction()->Flags().clean == 1u, + "alias normalization lost read identity or retained discarded EXEC provenance"); + struct Reads { uint32_t strict = 0; uint32_t ordinary = 0; bool dirty = false; } reads; + const SrtRuntime runtime{ + .read_memory = +[](void *data, uint64_t address, std::span words) { + ++static_cast(data)->ordinary; + if (address == 0x2000u) words[0] = 0x2222u; + else if (address == 0x3000u) words[0] = 0x8000u; + else if (address == 0x8000u) words[0] = 0xeeeeu; + else return false; + return true; + }, + .userdata = &reads, + .read_specialization_memory = +[](void *data, uint64_t address, + std::span words) { + auto &reads = *static_cast(data); + ++reads.strict; + if (reads.dirty) return false; + if (address == 0x3000u) words[0] = 0x1000u; + else if (address == 0x1000u) words[0] = 0x1111u; + else return false; + return true; + }}; + std::vector flat; + DescriptorValue descriptor; + { + SrtWalker clean(plan, CleanRuntime(runtime)); + SrtWalker walker(plan, runtime, &clean); + Check(walker.RefreshFlatBuffer(flat) && + flat == std::vector{0x1111u, 0x2222u, 0x1000u} && + walker.EvaluateDescriptor(0, descriptor) && descriptor.dwords[0] == 0x1111u && + reads.strict == 2 && reads.ordinary == 1, + "normalized aliases changed nested strict reads or repeated a shared read"); + } + Check(!SrtWalker(plan, runtime).RefreshFlatBuffer(flat), + "strict flat read accepted a missing clean evaluator"); + auto no_reader = runtime; + no_reader.read_specialization_memory = nullptr; + { + SrtWalker clean(plan, CleanRuntime(no_reader)); + Check(!SrtWalker(plan, no_reader, &clean).RefreshFlatBuffer(flat), + "strict flat read accepted a missing strict reader"); + } + Check(SrtWalker(plan, runtime).EvaluateDescriptor(0, descriptor) && + descriptor.dwords[0] == 0xeeeeu && reads.strict == 2 && reads.ordinary == 3, + "direct descriptor evaluation without a clean evaluator changed read domains"); + reads.dirty = true; + { + SrtWalker clean(plan, CleanRuntime(runtime)); + Check(!SrtWalker(plan, runtime, &clean).RefreshFlatBuffer(flat) && + reads.strict == 3 && reads.ordinary == 3, + "dirty strict pointer fell back to an ordinary read"); + } +} + void TestUniformVectorDescriptorRead() { using namespace Libs::Graphics::ShaderRecompiler::IR; Program program; @@ -285,16 +377,24 @@ void TestUniformVectorDescriptorRead() { } count.SetArg(4, Value(false)); auto &inactive = block.AppendNewInst(ValueOpcode::ReadFirstLane, {Value(&count), Value(false)}); + program.descriptor_sources.push_back({.dwords = {Value(&inactive)}, .dword_count = 1}); reads.strict = 0; uint32_t result = 99; - Check(SrtWalker(program, runtime).Evaluate(Value(&inactive), result) && - result == 0 && reads.strict == 0 && reads.ordinary == 0, - "literal false EXEC read vector memory"); + { + const auto plan = ExtractResourcePlan(program); + Check(SrtWalker(plan, runtime).Evaluate(plan.descriptor_sources.back().dwords[0], result) && + result == 0 && reads.strict == 0 && reads.ordinary == 0, + "literal false EXEC read vector memory"); + } count.SetArg(4, Value(true)); handle.SetArg(3, Value(0x204u)); - Check(SrtWalker(program, runtime).Evaluate(Value(&count), result) && - result == 0 && reads.strict == 0 && reads.ordinary == 0, - "invalid vector buffer format read memory"); + program.descriptor_sources.back().dwords[0] = Value(&count); + { + const auto plan = ExtractResourcePlan(program); + Check(SrtWalker(plan, runtime).Evaluate(plan.descriptor_sources.back().dwords[0], result) && + result == 0 && reads.strict == 0 && reads.ordinary == 0, + "invalid vector buffer format read memory"); + } handle.SetArg(3, Value(0x16204u)); count.SetArg(1, Value(&lane)); Check(!ValidateRuntimeValue(program, Value(&count)), @@ -567,6 +667,7 @@ void DbgExit(int) { std::abort(); } int main() { TestMappedSrtUsesDirectReaderByDefault(); TestIntegerRuntimeValueFollowsSrtReads(); + TestSrtAliasesRetainReadPolicy(); TestUniformVectorDescriptorRead(); TestExactReciprocalDescriptorArithmetic(); TestUnbasedFlatCacheHitMaterializes(); diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index d5d16a2b..e2970270 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -1758,17 +1758,18 @@ void TestRuntimeUnsignedMinDescriptor() { {descriptor, Value(0u), Value(0u), Value(0u), Value(true)}, fixture.AddMemory(memory, 0x330)); fixture.PlanAndTrack(); + auto plan = ExtractResourcePlan(fixture.program); std::array user_data{0xffffffffu}; SrtRuntime runtime{.user_data = user_data}; DescriptorValue value; const auto source = fixture.program.info.buffers[0].source; - Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(source, value) && + Check(SrtWalker(plan, runtime).EvaluateDescriptor(source, value) && value.dwords[3] == 0x100u, "runtime descriptor unsigned minimum did not clamp its first operand"); user_data[0] = 0x80u; Check( - SrtWalker(fixture.program, runtime).EvaluateDescriptor(source, value) && + SrtWalker(plan, runtime).EvaluateDescriptor(source, value) && value.dwords[3] == 0x80u, "runtime descriptor unsigned minimum did not preserve its first operand"); } @@ -1875,6 +1876,7 @@ void TestSampleAdjustSamplerScratch() { {image, shift_sampler, fixture.ImageAddress()}, fixture.AddMemory(memory, 0x200)); fixture.PlanAndTrack(); + auto plan = ExtractResourcePlan(fixture.program); const auto source = fixture.program.info.samplers[0].source; const auto stored = fixture.program.descriptor_sources[source] @@ -1886,12 +1888,12 @@ void TestSampleAdjustSamplerScratch() { std::array user_data{4u, 1u, 2u, 0x80000abcu}; SrtRuntime runtime{.user_data = user_data}; DescriptorValue descriptor; - Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(source, descriptor) && + Check(SrtWalker(plan, runtime).EvaluateDescriptor(source, descriptor) && descriptor.dwords[3] == 0x80000abcu, "SampleAdjust canonicalization lost sampler border fields"); const auto shift_source = fixture.program.info.samplers[ shift_sampler.Instruction()->Flags()].source; - Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(shift_source, descriptor) && + Check(SrtWalker(plan, runtime).EvaluateDescriptor(shift_source, descriptor) && std::ranges::equal(std::span(descriptor.dwords).first(4), std::array{0x36u, 0xfff000u, 0x02500000u, 0u}), "SampleAdjust shift-only scratch was not reduced to its zero border payload"); @@ -2239,6 +2241,7 @@ void TestSrtFlatteningAndRuntimeMemoization() { SrtRuntime runtime{.user_data = user_data, .read_memory = ReadTestMemory, .userdata = &memory}; + auto plan = ExtractResourcePlan(fixture.program); DescriptorValue descriptor; std::vector flat; const uint32_t request = fixture.program.info.buffers[0].source; @@ -2246,27 +2249,25 @@ void TestSrtFlatteningAndRuntimeMemoization() { SrtWalker walker(plan, runtime); return walker.EvaluateDescriptor(request, descriptor) && walker.RefreshFlatBuffer(flat); }; - Check(refresh(fixture.program), "typed runtime source evaluation failed"); + Check(refresh(plan), "typed runtime source evaluation failed"); Check(descriptor.dwords[0] == 0xdeadbeefu && flat == std::vector{0xdeadbeefu} && memory.reads == 1, "descriptor and flat SRT evaluation did not share one memoized read"); memory.reads = 0; memory.words[1] = 0x12345678u; - Check(refresh(fixture.program) && descriptor.dwords[0] == 0x12345678u && + Check(refresh(plan) && descriptor.dwords[0] == 0x12345678u && flat == std::vector{0x12345678u} && memory.reads == 1, "repeated runtime evaluation reused stale scalar memory"); memory.reads = 0; memory.fail_after = 0; - Check(!refresh(fixture.program), "unavailable scalar memory was accepted"); + Check(!refresh(plan), "unavailable scalar memory was accepted"); memory.fail_after = UINT32_MAX; - Check(refresh(fixture.program) && descriptor.dwords[0] == 0x12345678u && memory.reads == 1, + Check(refresh(plan) && descriptor.dwords[0] == 0x12345678u && memory.reads == 1, "failed runtime evaluation left a value marked as visiting"); - auto detached = ExtractResourcePlan(fixture.program); - Check(refresh(detached), "detached resource plan did not evaluate"); - auto moved = std::move(detached); + auto moved = std::move(plan); memory.reads = 0; memory.words[1] = 0x87654321u; Check(refresh(moved) && descriptor.dwords[0] == 0x87654321u && memory.reads == 1, @@ -2296,6 +2297,7 @@ void TestDynamicSrtReadRemainsExplicit() { {descriptor, Value(0u), Value(0u), Value(0u), Value(true)}, fixture.AddMemory(buffer, 8)); fixture.PlanAndTrack(); + auto plan = ExtractResourcePlan(fixture.program); Check(fixture.program.srt_reads.empty() && read.ResolveInstruction()->GetOpcode() == ValueOpcode::LoadAddressU32 && @@ -2309,7 +2311,7 @@ void TestDynamicSrtReadRemainsExplicit() { .read_memory = ReadTestMemory, .userdata = &memory}; DescriptorValue value; - Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(fixture.program.info.buffers[0].source, value) && + Check(SrtWalker(plan, runtime).EvaluateDescriptor(fixture.program.info.buffers[0].source, value) && value.dwords[0] == 0xabcdef01u && memory.reads == 1, "dynamic typed scalar descriptor source was not evaluated"); @@ -2475,7 +2477,7 @@ void TestConditionalSamplerPhi() { memory.fail_address = 0x1000 + (first == 448 / 4 ? 480u : 448u); DescriptorValue selected; SrtWalker clean(plan, CleanRuntime(runtime)); - Check(SrtWalker(plan, runtime, {}, &clean).EvaluateDescriptor(source, selected), + Check(SrtWalker(plan, runtime, &clean).EvaluateDescriptor(source, selected), "conditional sampler did not survive detached plan lifetime"); for (uint32_t word = 0; word < 4; ++word) { Check(selected.dwords[word] == memory.words[first + word], @@ -2487,13 +2489,13 @@ void TestConditionalSamplerPhi() { no_clean_reader.read_specialization_memory = nullptr; { SrtWalker clean(plan, CleanRuntime(no_clean_reader)); - Check(!SrtWalker(plan, no_clean_reader, {}, &clean).EvaluateDescriptor(source, selected), + Check(!SrtWalker(plan, no_clean_reader, &clean).EvaluateDescriptor(source, selected), "conditional sampler used unchecked memory for its predicate"); } memory.fail_address = 0x2000 + 196; { SrtWalker clean(plan, CleanRuntime(runtime)); - Check(!SrtWalker(plan, runtime, {}, &clean).EvaluateDescriptor(source, selected), + Check(!SrtWalker(plan, runtime, &clean).EvaluateDescriptor(source, selected), "conditional sampler ignored unavailable coherent predicate memory"); } } @@ -2544,6 +2546,7 @@ void TestFiniteImagePhiCycle() { fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress()}, fixture.AddMemory(memory, 4)); fixture.PlanAndTrack(); + auto plan = ExtractResourcePlan(fixture.program); const auto source = fixture.program.info.images[0].source; const auto &finite = fixture.program.descriptor_sources[source].indirect_descriptor; Check(finite && finite->sources.size() == 2, @@ -2558,7 +2561,7 @@ void TestFiniteImagePhiCycle() { "finite image key did not preserve the loop-carried selection"); std::array data; for (uint32_t i = 0; i < data.size(); ++i) data[i] = 100u + i; - SrtWalker walker(fixture.program, SrtRuntime{.user_data = data}); + SrtWalker walker(plan, SrtRuntime{.user_data = data}); for (uint32_t candidate = 0; candidate < 2; ++candidate) { DescriptorValue descriptor; Check(walker.EvaluateDescriptor(finite->sources[candidate], descriptor), @@ -2658,10 +2661,11 @@ void TestLoopCycleEnteredThroughRuntimeValue() { ConstantPropagationPass(fixture.program.blocks); RemoveIdentities(fixture.program.blocks); fixture.PlanAndTrack(); + auto plan = ExtractResourcePlan(fixture.program); const std::array user_data{0x4000u}; DescriptorValue descriptor; Check(fixture.program.info.buffers.size() == 1u && - SrtWalker(fixture.program, {.user_data = user_data}).EvaluateDescriptor( + SrtWalker(plan, {.user_data = user_data}).EvaluateDescriptor( fixture.program.info.buffers[0].source, descriptor) && descriptor.dwords[0] == user_data[0], "runtime-rooted invariant loop lost its buffer source"); @@ -2687,11 +2691,12 @@ void TestInvariantLoopPhi() { {handle, Value(0u), Value(0u), Value(0u), Value(true)}, fixture.AddMemory(memory, 4), loop); fixture.PlanAndTrack(); + auto plan = ExtractResourcePlan(fixture.program); std::array user_data{0x12345678u}; SrtRuntime runtime{.user_data = user_data}; DescriptorValue descriptor; - Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(fixture.program.info.buffers[0].source, descriptor) && + Check(SrtWalker(plan, runtime).EvaluateDescriptor(fixture.program.info.buffers[0].source, descriptor) && descriptor.dwords[0] == user_data[0], "loop-invariant descriptor phi was not evaluated through typed SSA"); } @@ -2799,10 +2804,11 @@ void TestBoundedRelativeRegisterWrites() { continue; } fixture.PlanAndTrack(); + auto plan = ExtractResourcePlan(fixture.program); const std::array user_data{0x1000u, 0u, 72u}; SrtRuntime runtime{.user_data = user_data}; DescriptorValue descriptor; - Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor( + Check(SrtWalker(plan, runtime).EvaluateDescriptor( fixture.program.info.buffers[0].source, descriptor) && descriptor.dwords[2] == 72u, "impossible relative writes left a false descriptor dependency"); diff --git a/tests/ScalarProvenanceTests.cpp b/tests/ScalarProvenanceTests.cpp index 946ec2b4..4e41a40b 100644 --- a/tests/ScalarProvenanceTests.cpp +++ b/tests/ScalarProvenanceTests.cpp @@ -3,6 +3,7 @@ #include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h" #include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h" #include "graphics/shader/recompiler/ir/passes/ResourceTracking.h" +#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/passes/SrtWalker.h" #include @@ -131,7 +132,7 @@ void TestImmediateFlatteningAndGvn() { TestMemory memory_image{{{0x1020u, 0xfeedbeefu}}}; SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image}; std::vector flat; - Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat), "flattened SRT walk failed"); + Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "flattened SRT walk failed"); Check(flat == std::vector{0xfeedbeefu} && memory_image.reads == 1, "flattened SRT did not evaluate its canonical read once"); } @@ -147,7 +148,7 @@ void TestRawScalarComponentAlignment() { TestMemory memory_image{{{0x1000u, 0x12345678u}}}; SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image}; std::vector flat; - Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat), "raw scalar SRT walk failed"); + Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "raw scalar SRT walk failed"); Check( flat == std::vector{0x12345678u} && memory_image.reads == 1, "raw scalar base, immediate, and offset were not aligned independently"); @@ -202,7 +203,7 @@ void TestNestedSrtWalk() { TestMemory memory_image{{{0x1000u, 0x2000u}, {0x2000u, 0xabcdef01u}}}; SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image}; std::vector flat; - Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat), "nested SRT walk failed"); + Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat), "nested SRT walk failed"); Check(flat == std::vector({0x2000u, 0xabcdef01u}), "nested typed SRT reads were not evaluated in dependency order"); } @@ -225,7 +226,7 @@ void TestShaderBaseAndUserData() { SrtRuntime runtime{.user_data = user_data, .shader_base = 0x12345678abcdef00ull}; DescriptorValue result; - Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(0, result), + Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).EvaluateDescriptor(0, result), "shader-relative descriptor evaluation failed"); Check(result.dword_count == 3 && result.dwords[0] == 0xabcdef00u && result.dwords[1] == 0x12345678u && result.dwords[2] == 0x24u, @@ -250,7 +251,7 @@ void TestCarryAndBitFields() { {.dwords = {low, high, inserted, sign}, .dword_count = 4}); DescriptorValue result; - Check(SrtWalker(fixture.program, {}).EvaluateDescriptor(0, result), + Check(SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result), "carry and bit-field descriptor evaluation failed"); Check(result.dwords[0] == 1u && result.dwords[1] == 1u && result.dwords[2] == 0x89abcdefu && result.dwords[3] == 0xffffffffu, @@ -273,11 +274,12 @@ void TestInvariantAndDivergentPhi() { fixture.program.descriptor_sources.push_back( {.dwords = {Value(&divergent)}, .dword_count = 1}); + const auto plan = ExtractResourcePlan(fixture.program); DescriptorValue result; - Check(SrtWalker(fixture.program, {}).EvaluateDescriptor(0, result) && + Check(SrtWalker(plan, {}).EvaluateDescriptor(0, result) && result.dwords[0] == 7u, "loop-invariant typed phi was rejected"); - Check(!SrtWalker(fixture.program, {}).EvaluateDescriptor(1, result), + Check(!SrtWalker(plan, {}).EvaluateDescriptor(1, result), "divergent phi was accepted"); } @@ -374,7 +376,7 @@ void TestRuntime64BitDescriptorOps() { {.dwords = {low, high}, .dword_count = 2}); DescriptorValue result; - Check(SrtWalker(fixture.program, {}).EvaluateDescriptor(0, result) && + Check(SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result) && result.dwords[0] == 0xabcdu && result.dwords[1] == 0x1235u, "64-bit typed descriptor arithmetic evaluation is incorrect"); } @@ -447,12 +449,13 @@ void TestUniformFirstLaneSamplerLod() { std::array user_data{}; user_data[6] = 3u; SrtRuntime runtime{.user_data = user_data}; + const auto plan = ExtractResourcePlan(fixture.program); DescriptorValue result; - Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(0, result) && + Check(SrtWalker(plan, runtime).EvaluateDescriptor(0, result) && result.dwords[0] == 0x00300300u, "uniform sampler LOD evaluated incorrectly"); user_data[6] = 20u; - Check(SrtWalker(fixture.program, runtime).EvaluateDescriptor(0, result) && + Check(SrtWalker(plan, runtime).EvaluateDescriptor(0, result) && result.dwords[0] == 0x00ffffffu, "uniform sampler LOD clamp evaluated incorrectly"); } @@ -487,11 +490,12 @@ void TestFloatComparisonDescriptorInputs() { fixture.program.descriptor_sources.push_back( {.dwords = {low, high}, .dword_count = 2}); + const auto plan = ExtractResourcePlan(fixture.program); for (size_t index = 0; index < inputs.size(); index++) { const auto &input = inputs[index]; const std::array user_data{input.bits}; DescriptorValue result; - Check(SrtWalker(fixture.program, {.user_data = user_data}) + Check(SrtWalker(plan, {.user_data = user_data}) .EvaluateDescriptor(0, result) && result.dwords[0] == (flush && index < 4 ? 1 : input.less_equal) && result.dwords[1] == (flush && index < 4 ? 1 : input.greater_equal), @@ -566,7 +570,7 @@ void TestConstantBufferBounds() { TestMemory memory_image{{{0x3000u, 0x12345678u}, {0x300cu, 0xa5a5a5a5u}}}; SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image}; std::vector flat; - Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat) == test.valid, + Check(SrtWalker(ExtractResourcePlan(fixture.program), runtime).RefreshFlatBuffer(flat) == test.valid, "constant-buffer walk misaligned or wrapped its offset components"); Check(test.valid ? flat == std::vector{test.expected} : memory_image.reads == 0, "constant-buffer walk read the wrong word or accessed an out-of-bounds address"); @@ -690,7 +694,7 @@ void TestUndefinedRuntimeValueFails() { fixture.program.descriptor_sources.push_back( {.dwords = {undef}, .dword_count = 1}); DescriptorValue result; - Check(!SrtWalker(fixture.program, {}).EvaluateDescriptor(0, result), + Check(!SrtWalker(ExtractResourcePlan(fixture.program), {}).EvaluateDescriptor(0, result), "undefined typed descriptor source was accepted"); } diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 9e205876..67f7ee66 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -21,6 +21,7 @@ #include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h" #include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h" #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" @@ -10564,14 +10565,16 @@ void TestMergedShaderUserDataSnapshot() { const auto &program = translated.program; Check(program.info.buffers.size() == 2 && program.srt_reads.size() == 4, "merged shader lost front user SGPRs or the back-stage SRT load"); + 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); IR::DescriptorValue front_descriptor, back_descriptor; const auto &table = params == &first ? first_table : second_table; - Check(IR::SrtWalker(program, runtime).EvaluateDescriptor(program.info.buffers[0].source, front_descriptor) && - IR::SrtWalker(program, runtime).EvaluateDescriptor(program.info.buffers[1].source, back_descriptor) && + Check(walker.EvaluateDescriptor(program.info.buffers[0].source, front_descriptor) && + walker.EvaluateDescriptor(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()), @@ -13743,7 +13746,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { ShaderRecompiler::IR::SrtRuntime carry_runtime{carry_user_data, shader_base, nullptr, nullptr}; ShaderRecompiler::IR::DescriptorValue carry_value; - Check(ShaderRecompiler::IR::SrtWalker(carry_ir, carry_runtime).EvaluateDescriptor(carry_source_index, 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(expected_pc) && carry_value.dwords[1] == static_cast(expected_pc >> 32u), "S_GETPC shader-base or add/addc carry evaluation was incorrect"); @@ -13813,7 +13816,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { TypedDescriptorSource(inline_sampler_ir, inline_sampler_ir.info.samplers[0].source) != nullptr && - ShaderRecompiler::IR::SrtWalker(inline_sampler_ir, runtime).EvaluateDescriptor(inline_sampler_ir.info.samplers[0].source, sampler) && + ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(inline_sampler_ir), runtime).EvaluateDescriptor(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 " @@ -13849,7 +13852,7 @@ void TestSrtWalkerRealSmemTranslation() { std::vector flat; const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword, nullptr}; - const auto walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat); + const auto walked = ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat); Check(walked, "SRT walk failed"); Check(flat.size() == table.size() && std::equal(flat.begin(), flat.end(), table.begin()), @@ -13878,7 +13881,7 @@ void TestSrtWalkerVccBaseTranslation() { const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword, nullptr}; std::vector flat; - Check(ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat), "SRT walk failed"); + Check(ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat), "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"); @@ -13906,14 +13909,15 @@ void TestSrtWalkerRealSBufferTranslation() { std::vector flat; const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword, nullptr}; - const auto walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat); + const auto plan = ShaderRecompiler::IR::ExtractResourcePlan(ir); + const auto walked = ShaderRecompiler::IR::SrtWalker(plan, runtime).RefreshFlatBuffer(flat); 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(ir, runtime).RefreshFlatBuffer(flat); + const auto bounds_walked = ShaderRecompiler::IR::SrtWalker(plan, runtime).RefreshFlatBuffer(flat); Check(!bounds_walked, "real S_BUFFER_LOAD walk ignored descriptor bounds"); CheckFlattenedReadSlots( ir, 4, "real S_BUFFER_LOAD patch used the wrong flat offsets"); @@ -13928,7 +13932,7 @@ void TestSrtWalkerRealSBufferTranslation() { static_cast(std::size(negative_shader)), negative_ir); user_data[10] = sizeof(table); - Check(!ShaderRecompiler::IR::SrtWalker(negative_ir, runtime).RefreshFlatBuffer(flat), + Check(!ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(negative_ir), runtime).RefreshFlatBuffer(flat), "real S_BUFFER_LOAD walk accepted a negative immediate"); } @@ -13967,7 +13971,7 @@ void TestScalarMemorySourcesCapturedBeforeWrites() { const ShaderRecompiler::IR::SrtRuntime runtime{ user_data, 0, ReadSrtHostRangeDword, &range}; std::vector flat; - Check(ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat), "SRT walk failed"); + Check(ShaderRecompiler::IR::SrtWalker(ShaderRecompiler::IR::ExtractResourcePlan(ir), runtime).RefreshFlatBuffer(flat), "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 "