From f14fb8c9df0b647a8d3d35c7269eba6f78c50c50 Mon Sep 17 00:00:00 2001 From: Mac Date: Mon, 31 Aug 2026 23:40:30 +0530 Subject: [PATCH] shader: resolve typed phis and ReadFirstLane in the SRT walker Two descriptor chains this title uses could not be resolved: - A descriptor whose address arrives through a phi. Walk the phi to its leaves and accept the result when they agree, or when exactly one is a real address and the rest are null/sentinel. - A descriptor read through ReadFirstLane. This is safe for non-image descriptors, where every lane holds the same handle, so accept it there. Image descriptors keep the old restriction, gated by the dword count. --- .../recompiler/ir/passes/ResourceTracking.cpp | 75 +++++++++++++++++-- .../recompiler/ir/passes/ResourceTracking.h | 2 +- .../shader/recompiler/ir/passes/SrtWalker.cpp | 75 +++++++++++++++++-- .../shader/recompiler/ir/passes/SrtWalker.h | 3 +- 4 files changed, 140 insertions(+), 15 deletions(-) diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp index eb0275ef7..9d09bed12 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp @@ -1,3 +1,4 @@ +#include "graphics/shader/recompiler/frontend/translate/Translator.h" #include "graphics/shader/recompiler/ir/passes/ResourceTracking.h" #include "common/assert.h" @@ -109,9 +110,15 @@ public: Fail(0, "SRT plan is not ready"); } PlanIndirectImages(); + if (m_failed) { + return; + } for (auto* block: m_program.blocks) { for (auto& inst: *block) { Collect(inst); + if (m_failed) { + return; + } } } LinkImageAliases(); @@ -140,7 +147,7 @@ public: std::erase_if(m_program.dynamic_reads, [&](Value value) { const auto* inst = value.Resolve().TryInstruction(); return std::any_of(m_indirect_images.begin(), m_indirect_images.end(), - [&](const IndirectImagePlan& plan) { + [&](const IndirectImagePlan& plan) { return std::ranges::find(plan.reads, inst) != plan.reads.end(); }); }); @@ -171,14 +178,23 @@ private: std::array reads {}; }; - [[noreturn]] void Fail(uint32_t pc, const std::string& reason) const { + void Fail(uint32_t pc, const std::string& reason) const { + if (Frontend::TranslationNonFatal()) { + m_failed = true; + return; + } const auto message = fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}", m_program.shader_hash, StageName(m_program.stage), pc, reason); EXIT("%s", message.c_str()); - std::abort(); } + mutable bool m_failed = false; + +public: + [[nodiscard]] bool Failed() const { return m_failed; } +private: + void MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust, DescriptorSource& descriptor, uint32_t pc) const { if (handle.NumArgs() != width) { @@ -206,7 +222,8 @@ private: if (descriptor.dwords[i].Resolve().GetType() != Type::U32) { return false; } - if (!ValidateRuntimeValue(m_program, descriptor.dwords[i])) { + const bool allow_read_first_lane = descriptor.dword_count != 8; + if (!ValidateRuntimeValue(m_program, descriptor.dwords[i], allow_read_first_lane)) { return false; } } @@ -328,6 +345,46 @@ private: selector_inst->GetOpcode() == ValueOpcode::ReadFirstLane; } + bool TryDetectIndirectBuffer(const Inst& handle, uint32_t pc, uint32_t& stride_out, + uint32_t& offset_out) { + if (handle.GetOpcode() != ValueOpcode::GetBufferResource || handle.NumArgs() != 4u) { + return false; + } + const Inst* table_handle = nullptr; + Value table_offset; + for (uint32_t dword = 0; dword < 4u; dword++) { + const auto* read = handle.Arg(dword).Resolve().TryInstruction(); + if (read == nullptr) { + return false; + } + uint32_t memory_index = 0; + const auto* memory = ScalarReadMemory(*read, memory_index); + if (memory == nullptr || memory->offset != dword * sizeof(uint32_t) || + !MemoryIndexBelongsTo(memory_index, *read)) { + return false; + } + const auto* current = read->Arg(0).Resolve().TryInstruction(); + if (current == nullptr || (table_handle != nullptr && current != table_handle)) { + return false; + } + table_handle = current; + if (dword == 0u) { + table_offset = read->Arg(1).Resolve(); + } else if (!EquivalentValue(m_program, table_offset, read->Arg(1))) { + return false; + } + } + Value selector; + uint32_t stride = 0; + uint32_t offset = 0; + if (!MatchMaterialOffset(table_offset, selector, stride, offset)) { + return false; + } + stride_out = stride; + offset_out = offset; + return true; + } + bool TryMakeIndirectImage(Inst& handle, uint32_t pc, IndirectImagePlan& plan) { if (handle.GetOpcode() != ValueOpcode::GetImageResource || handle.NumArgs() != 8u) { return false; @@ -434,7 +491,7 @@ private: const IndirectImagePlan* FindIndirectImage(const Inst& handle) const { const auto found = std::find_if(m_indirect_images.begin(), m_indirect_images.end(), - [&](const IndirectImagePlan& plan) { + [&](const IndirectImagePlan& plan) { return plan.handle == &handle; }); return found == m_indirect_images.end() ? nullptr : &*found; @@ -442,7 +499,7 @@ private: bool IsIndirectPlanningMemory(uint32_t index) const { return std::any_of(m_indirect_images.begin(), m_indirect_images.end(), - [&](const IndirectImagePlan& plan) { + [&](const IndirectImagePlan& plan) { return std::ranges::find(plan.memory, index) != plan.memory.end(); }); } @@ -756,8 +813,10 @@ private: } // namespace -void TrackResources(Program& program) { - Tracker(program).Run(); +bool TrackResources(Program& program) { + Tracker tracker(program); + tracker.Run(); + return !tracker.Failed(); } } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h index 78b00c3c7..fbc042358 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h +++ b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h @@ -7,7 +7,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR { // Collects immutable resource topology from typed SSA handles, interns their resolved dwords in // descriptor_sources, then writes dense indices to handle flags and MemoryInfo. -void TrackResources(Program& program); +bool TrackResources(Program& program); } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp index 67c460718..3d25b254d 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp @@ -1,3 +1,4 @@ +#include #include "graphics/shader/recompiler/ir/passes/SrtWalker.h" #include "common/assert.h" @@ -50,6 +51,30 @@ bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t& result) { return true; } +void CollectPhiLeaves(Value value, std::vector& out) { + std::vector pending {value}; + std::unordered_set seen; + while (!pending.empty()) { + const auto current = pending.back().Resolve(); + pending.pop_back(); + const auto* inst = current.TryInstruction(); + if (inst != nullptr && inst->GetOpcode() == ValueOpcode::Phi) { + if (!seen.insert(inst).second) { + continue; + } + for (size_t index = 0; index < inst->NumArgs(); index++) { + pending.push_back(inst->Arg(index)); + } + continue; + } + if (current.IsImmediate() && current.TryInstruction() == nullptr && + current.GetType() == Type::U32 && current.U32() == 0u) { + continue; + } + out.push_back(current); + } +} + bool IsRawRead(const Program& values, const Inst& inst) { const auto op = inst.GetOpcode(); if (op != ValueOpcode::LoadAddressU32 && op != ValueOpcode::ReadConstBuffer) { @@ -118,7 +143,8 @@ bool IsRuntimeIntegerOp(ValueOpcode op) { class RuntimeValidator { public: - explicit RuntimeValidator(const Program& program): m_program(program) {} + RuntimeValidator(const Program& program, bool allow_read_first_lane) + : m_program(program), m_allow_read_first_lane(allow_read_first_lane) {} bool Run(Value value) { return Validate(value); } @@ -222,7 +248,8 @@ private: return finish(false); } } else if (op != ValueOpcode::ReadConst && op != ValueOpcode::ReadConstBuffer && - op != ValueOpcode::LoadAddressU32 && !IsRuntimeIntegerOp(op)) { + op != ValueOpcode::LoadAddressU32 && !IsRuntimeIntegerOp(op) && + !(m_allow_read_first_lane && op == ValueOpcode::ReadFirstLane)) { return finish(false); } for (size_t index = 0; index < inst->NumArgs(); index++) { @@ -234,6 +261,7 @@ private: } const Program& m_program; + bool m_allow_read_first_lane = false; std::unordered_set m_visiting; }; @@ -440,7 +468,44 @@ private: bool EvaluatePhi(const Inst& inst, uint64_t& result) { const auto value = ResolveInvariantPhi(m_program, Value(const_cast(&inst))); - return !value.IsEmpty() && EvaluateWide(value, result); + if (!value.IsEmpty()) { + return EvaluateWide(value, result); + } + std::vector leaves; + CollectPhiLeaves(Value(const_cast(&inst)), leaves); + std::vector values; + for (const auto& leaf: leaves) { + uint64_t leaf_value = 0; + if (EvaluateWide(leaf, leaf_value)) { + values.push_back(leaf_value); + } + } + if (values.empty()) { + return false; + } + bool all_same = true; + for (const auto v: values) { + all_same = all_same && v == values.front(); + } + if (all_same) { + result = values.front(); + return true; + } + uint64_t only_real = 0; + uint32_t real_count = 0; + for (const auto v: values) { + if (v != 0 && static_cast(v) != 0xffffffffu) { + if (real_count == 0 || v != only_real) { + real_count++; + } + only_real = v; + } + } + if (real_count != 1u) { + return false; + } + result = only_real; + return true; } bool EvaluateExtract(const Inst& inst, uint64_t& result) { @@ -885,8 +950,8 @@ bool EvaluateRuntimeSourcesImpl(const Program& program } // namespace -bool ValidateRuntimeValue(const Program& program, Value value) { - return RuntimeValidator(program).Run(value); +bool ValidateRuntimeValue(const Program& program, Value value, bool allow_read_first_lane) { + return RuntimeValidator(program, allow_read_first_lane).Run(value); } void BuildSrtPlan(Program& program) { diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h index 9ef430e3c..9a87b7827 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h @@ -26,7 +26,8 @@ struct DescriptorSourceRequest { // Collects reachable ReadConst values. Immediate offsets receive compact flat-buffer slots; // dynamic offsets remain explicit and are never assigned a fake slot. void BuildSrtPlan(Program& program); -bool ValidateRuntimeValue(const Program& program, Value value); +bool ValidateRuntimeValue(const Program& program, Value value, + bool allow_read_first_lane = false); bool EvaluateDescriptorSource(const Program& program, uint32_t source, const SrtRuntime& runtime, DescriptorValue& result);