shader: walk every resource plan with the x86 SRT walker

This commit is contained in:
nmzik
2026-10-24 02:57:24 +02:00
parent bdf48dcf3b
commit d7856753e0
12 changed files with 1782 additions and 1701 deletions
@@ -584,10 +584,13 @@ void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
for (auto& inst: block) {
inst.SetParent(nullptr);
if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 || inst.GetOpcode() == ValueOpcode::ReadConstBuffer)
inst.SetFlags(SrtReadFlags {.index = inst.Flags<MemoryFlags>().index});
inst.SetFlags(MemoryFlags {.index = inst.Flags<MemoryFlags>().index});
}
query.value_storage.splice(query.value_storage.end(), block.Instructions());
source.call_targets = std::move(static_cast<ResourcePlan&>(query));
std::vector<Value> targets;
for (const auto& site: source.calls) targets.insert(targets.end(), site.target.begin(), site.target.end());
CompileSrtWalker(source.call_targets, targets);
}
} // namespace
@@ -617,12 +620,18 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
EXIT_IF(source.unsupported_call_pc != UINT32_MAX);
auto& reads = source.reads;
reads.clear();
IR::SrtReadCapture capture(runtime, reads);
const auto clean = IR::CleanRuntime(capture.ObservedRuntime());
IR::SrtWalker walker(source.call_targets, clean);
std::vector<uint32_t> flat;
const bool walked = IR::WalkSrt(source.call_targets, runtime, flat, &reads);
const auto read_code = [&](uint64_t address, std::span<uint32_t> words) {
if (runtime.read_specialization_memory == nullptr ||
!runtime.read_specialization_memory(runtime.userdata, address, words)) return false;
reads.emplace_back(address, words.size_bytes());
return true;
};
for (auto& site: source.calls) {
uint32_t low = 0, high = 0;
if (!walker.Evaluate(site.target[0], low) || !walker.Evaluate(site.target[1], high))
if (!walked || !IR::WalkedValue(source.call_targets, site.target[0], low) ||
!IR::WalkedValue(source.call_targets, site.target[1], high))
EXIT("shader call at pc 0x%08x has an unavailable scalar target",
source.decoded.instructions[site.instruction].pc);
// SWAPPC reads the old aliased pair and ignores the target's low two bits.
@@ -646,7 +655,7 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
break;
}
linked.observed_function.resize(function.size);
if (!clean.read_memory(clean.userdata, source.calls[i].address, linked.observed_function) ||
if (!read_code(source.calls[i].address, linked.observed_function) ||
!std::ranges::equal(expected, linked.observed_function)) {
unchanged = false;
break;
@@ -676,8 +685,7 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
uint32_t count = 1;
Decoder::Instruction inst;
for (;;) {
if (!clean.read_memory(clean.userdata, address + uint64_t {index} * sizeof(uint32_t),
std::span(words).first(count)))
if (!read_code(address + uint64_t {index} * sizeof(uint32_t), std::span(words).first(count)))
EXIT("shader call at pc 0x%08x cannot read leaf code", call.pc);
inst = {};
Decoder::DecodeInstruction(words, 0u, inst);
+5 -24
View File
@@ -14,7 +14,6 @@
#include <array>
#include <bit>
#include <deque>
#include <list>
#include <memory>
#include <optional>
@@ -28,8 +27,6 @@ class CodeGenerator;
namespace Libs::Graphics::ShaderRecompiler::IR {
class SrtWalker;
// Finite descriptor domains also bound the arithmetic used to prove their indices.
constexpr uint32_t MaxIndirectDescriptorProbes = 65536u;
@@ -663,19 +660,6 @@ struct ResourcePlan {
bool resolved = false;
};
struct EvaluationContext {
struct Entry {
uint64_t value = 0;
uint64_t generation = 0;
};
std::vector<Entry> values;
uint64_t generation = 0;
const SrtWalker* session = nullptr;
Value active_mask;
uint8_t read_policy = 0;
};
ResourcePlan();
~ResourcePlan();
@@ -695,21 +679,18 @@ struct ResourcePlan {
std::vector<ResourceBlock> control_flow;
std::unordered_map<const Inst*, std::vector<uint32_t>> control_phi_predecessors;
std::vector<SrtRead> srt_reads;
bool requires_specialization_memory = false;
std::vector<std::pair<uint64_t, uint64_t>> source_reads;
bool capture_specialization_reads = false;
bool srt_plan_complete = false;
bool resource_tracking_complete = false;
ShaderInfo info;
UniformFillPlan uniform_fill;
// x86 walker over the retained SRT, descriptor and condition values.
std::unique_ptr<Xbyak::CodeGenerator> srt_code;
// GPU-thread scratch for nested clean/EXEC memos, activity and material keys.
mutable std::deque<EvaluationContext> evaluation_contexts;
mutable uint32_t evaluation_value_count = 0;
mutable std::vector<uint8_t> active_sources;
mutable std::vector<uint8_t> visited_blocks;
mutable std::vector<uint8_t> reachable_blocks;
mutable std::vector<uint32_t> pending_blocks;
uint32_t srt_user_data = 0; // User-data DWORDs the walker reads.
uint32_t srt_captures = 0; // Scratch offset of loaded addresses.
// GPU-thread scratch for walker results and material keys.
mutable std::vector<uint64_t> srt_scratch;
mutable std::vector<uint32_t> material_keys;
mutable std::vector<SelectorKeys> selector_keys;
};
+4 -8
View File
@@ -123,13 +123,9 @@ public:
[[nodiscard]] Block* PhiBlock(size_t index) const;
[[nodiscard]] Block* Parent() const;
[[nodiscard]] const std::vector<Use>& Uses() const;
// Runtime indices belong to the resource plan that owns this instruction.
[[nodiscard]] uint32_t EvaluationIndex(uint32_t& count) const {
if (evaluation_index == UINT32_MAX) {
evaluation_index = count++;
}
return evaluation_index;
}
// SRT walker output index, assigned by the resource plan that owns this instruction.
[[nodiscard]] uint32_t WalkOutput() const { return walk_output; }
void SetWalkOutput(uint32_t index) const { walk_output = index; }
// Backend definitions belong to one emission. Wave64 emulation needs one per half.
[[nodiscard]] uint32_t Definition(uint32_t half) const { return definitions[half]; }
void SetDefinition(uint32_t half, uint32_t definition) const {
@@ -171,7 +167,7 @@ private:
ValueOpcode opcode;
uint8_t num_args;
bool live = false;
mutable uint32_t evaluation_index = UINT32_MAX;
mutable uint32_t walk_output = UINT32_MAX;
mutable std::array<uint32_t, 2> definitions {};
uint64_t flags;
Block* parent = nullptr;
@@ -186,35 +186,6 @@ const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
return &program.descriptor_sources[source];
}
void MarkCleanReads(const std::unordered_set<const Inst*>& planned_reads,
const DescriptorSource* source, Value extra = {}) {
if (source == nullptr && extra.IsEmpty()) {
return;
}
std::vector<Value> pending;
if (source != nullptr) {
pending.assign(source->dwords.begin(), source->dwords.begin() + source->dword_count);
}
if (!extra.IsEmpty()) pending.push_back(extra);
std::vector<const Inst*> visited;
while (!pending.empty()) {
auto* inst = pending.back().Resolve().TryInstruction();
pending.pop_back();
if (inst == nullptr || std::ranges::find(visited, inst) != visited.end()) {
continue;
}
visited.push_back(inst);
if (planned_reads.contains(inst)) {
auto flags = inst->Flags<SrtReadFlags>();
flags.clean = 1u;
inst->SetFlags(flags);
}
for (size_t arg = 0; arg < inst->NumArgs(); arg++) {
pending.push_back(inst->Arg(arg));
}
}
}
bool ReadTable(uint64_t base, uint64_t size, uint64_t offset,
const SrtRuntime& runtime, std::span<uint32_t> words) {
const auto count = std::min<uint64_t>(words.size(), offset < size ? (size - offset) / 4u : 0u);
@@ -309,22 +280,22 @@ bool CanReadIndexedSelector(const ShaderBufferResource& material, uint32_t offse
uint64_t {UINT32_MAX} + 1u;
}
bool EvaluateIndirectCount(const DescriptorSource::IndirectDescriptor& indirect,
const SrtRuntime& runtime, SrtWalker& clean, uint32_t& count) {
bool EvaluateIndirectCount(const ResourcePlan& program, const DescriptorSource::IndirectDescriptor& indirect,
const SrtRuntime& runtime, uint32_t& count) {
if (indirect.workgroup_axis != UINT32_MAX) {
if (runtime.workgroup_counts.size() != 3u || indirect.workgroup_axis >= 3u) return false;
count = std::ranges::find(runtime.workgroup_counts, 0u) != runtime.workgroup_counts.end()
? 0u : runtime.workgroup_counts[indirect.workgroup_axis];
return true;
}
if (!clean.Evaluate(indirect.key_count, count)) return false;
if (!WalkedValue(program, indirect.key_count, count)) return false;
if (indirect.signed_count && std::bit_cast<int32_t>(count) < 0) count = 0;
return true;
}
bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::IndirectDescriptor& indirect,
const DescriptorValue& material_value, const SrtRuntime& runtime,
SrtWalker& clean, std::vector<uint32_t>& keys) {
std::vector<uint32_t>& keys) {
const auto* selector = &*indirect.selector;
keys.clear();
const auto read_selector = [&](uint64_t base, uint64_t size, uint64_t offset,
@@ -363,7 +334,7 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
const auto& rows = *selector->rows;
DescriptorValue count_value;
ShaderBufferResource counts, material;
if (!clean.EvaluateDescriptor(rows.source, count_value) ||
if (!WalkedDescriptor(program, rows.source, count_value) ||
!DecodeBufferDescriptor(count_value, counts) ||
!DecodeBufferDescriptor(material_value, material) || selector->stride != 4u ||
selector->offset != 0u || rows.stride == 0u || (rows.stride & 3u) != 0u ||
@@ -392,7 +363,7 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
if (candidate_end - first > MaxIndirectDescriptorProbes) break;
end = candidate_end;
}
// Coalesce small gaps, but interpret only selected prefixes. The strict
// Coalesce small gaps, but interpret only selected prefixes. The specialization
// reader captures the entire fetched span for writable-alias rejection.
batch.resize(end - first);
if (!ReadScalarTable(material.Base48(), material.GetSize(), first * 4u, runtime, batch))
@@ -408,8 +379,8 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
std::array<uint32_t, 4> masks {};
uint32_t first = 0, count = 0;
if (!DecodeBufferDescriptor(material_value, material) ||
!clean.Evaluate(indirect.selector_first, first) ||
!EvaluateIndirectCount(indirect, runtime, clean, count) || count != masks.size() * 32u ||
!WalkedValue(program, indirect.selector_first, first) ||
!EvaluateIndirectCount(program, indirect, runtime, count) || count != masks.size() * 32u ||
selector->stride != sizeof(masks) || selector->offset != 0u ||
material.Type() != 0u || material.Stride() != sizeof(masks) ||
material.SwizzleEnabled() || material.AddTid() || material.OutOfBounds() != 0u ||
@@ -462,8 +433,8 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
base = material.Base48();
size = material.GetSize();
}
if (!clean.Evaluate(indirect.selector_first, first) ||
!EvaluateIndirectCount(indirect, runtime, clean, count) || count > MaxIndirectDescriptorProbes)
if (!WalkedValue(program, indirect.selector_first, first) ||
!EvaluateIndirectCount(program, indirect, runtime, count) || count > MaxIndirectDescriptorProbes)
return false;
// SMEM uses its instruction's byte stride; V# stride only supplies its bounds.
const auto step = selector->stride != 0u ? selector->stride : material.Stride();
@@ -483,8 +454,8 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
uint32_t mask = 0;
uint32_t count = 0;
if (material_value.dword_count != 2u ||
!clean.Evaluate(indirect.selector_mask, mask) ||
!EvaluateIndirectCount(indirect, runtime, clean, count) || count == 0u || count > 32u) {
!WalkedValue(program, indirect.selector_mask, mask) ||
!EvaluateIndirectCount(program, indirect, runtime, count) || count == 0u || count > 32u) {
return false;
}
if (count < 32u) mask &= (1u << count) - 1u;
@@ -541,8 +512,7 @@ template <typename Specialization, typename Normalize>
bool MaterializeIndirectDescriptor(const ResourcePlan& program,
uint32_t source_index,
uint32_t resource_index, uint32_t dword_count, uint32_t read_dwords,
const SrtRuntime& runtime, SrtWalker& clean,
ResourceSnapshot& snapshot,
const SrtRuntime& runtime, ResourceSnapshot& snapshot,
std::vector<DescriptorValue>& descriptors,
std::vector<Specialization>& specializations,
Normalize&& normalize) {
@@ -605,8 +575,8 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
if (selector == nullptr) keys.clear();
DescriptorValue material_value;
if ((selector != nullptr && (byte_keys || !selector_keys->resolved) &&
!clean.EvaluateDescriptor(selector->source, material_value)) ||
!clean.EvaluateDescriptor(indirect.table_source, table_value)) {
!WalkedDescriptor(program, selector->source, material_value)) ||
!WalkedDescriptor(program, indirect.table_source, table_value)) {
return false;
}
if (table_value.dword_count == 2u) {
@@ -659,7 +629,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
: uint64_t {1} << 32u;
if (!indirect.key_count.IsEmpty()) {
uint32_t key_count = 0u;
if (!EvaluateIndirectCount(indirect, runtime, clean, key_count)) return false;
if (!EvaluateIndirectCount(program, indirect, runtime, key_count)) return false;
domain = std::min(domain, uint64_t {key_count});
}
if (indexed_pointer &&
@@ -771,8 +741,8 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
if (selector == nullptr) {
uint32_t key_count = 0;
uint32_t first_key = 0;
if (!EvaluateIndirectCount(indirect, runtime, clean, key_count)) return false;
if (!indirect.selector_first.IsEmpty() && !clean.Evaluate(indirect.selector_first, first_key))
if (!EvaluateIndirectCount(program, indirect, runtime, key_count)) return false;
if (!indirect.selector_first.IsEmpty() && !WalkedValue(program, indirect.selector_first, first_key))
return false;
if (indirect.table_stride == 0u || key_count > MaxIndirectDescriptorProbes ||
(indirect.selector_first.IsEmpty() && key_count != 0u &&
@@ -788,7 +758,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
} else {
if (!indirect.selector_mask.IsEmpty() && table_value.dword_count != 2u) return false;
if (!selector_keys->resolved) {
if (!ResolveSelectorKeys(indirect, material_value, runtime, clean, keys)) return false;
if (!ResolveSelectorKeys(program, indirect, material_value, runtime, keys)) return false;
selector_keys->resolved = true;
}
}
@@ -827,7 +797,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
std::span(candidate.dwords).first(read_dwords))) {
return read_failed("table descriptor read", key, 0u, table_base, table_offset, descriptor_bytes);
}
} else if (!clean.EvaluateDescriptor(sources[entry], candidate)) {
} else if (!WalkedDescriptor(program, sources[entry], candidate)) {
return false;
}
if (!normalize(candidate))
@@ -1380,16 +1350,13 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
if (const auto found = cloned.find(source); found != cloned.end()) {
return Value(found->second);
}
if (source->GetOpcode() == ValueOpcode::LoadBufferU32) {
plan.requires_specialization_memory = true;
plan.capture_specialization_reads = true;
}
if (source->GetOpcode() == ValueOpcode::LoadBufferU32) plan.capture_specialization_reads = true;
auto& target =
plan.value_storage.emplace_back(source->GetOpcode(), source->Flags<uint64_t>());
if (source->GetOpcode() == ValueOpcode::LoadAddressU32 ||
source->GetOpcode() == ValueOpcode::ReadConstBuffer ||
source->GetOpcode() == ValueOpcode::LoadBufferU32) {
target.SetFlags(SrtReadFlags {.index = source->Flags<MemoryFlags>().index});
target.SetFlags(MemoryFlags {.index = source->Flags<MemoryFlags>().index});
}
cloned.emplace(source, &target);
if (source->GetOpcode() == ValueOpcode::Phi) {
@@ -1411,7 +1378,6 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
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 =
@@ -1523,57 +1489,16 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
plan.uniform_fill.element_count = Clone(plan.uniform_fill.element_count);
plan.capture_specialization_reads |=
capture_indirect_reads || capture_control_reads || !plan.source_reads.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<const Inst*> 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));
if (indirect.selector->rows)
MarkCleanReads(planned_reads, Source(plan, indirect.selector->rows->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) MarkCleanReads(planned_reads, Source(plan, buffer.source));
}
for (const auto& image: plan.info.images) {
if (image.written) MarkCleanReads(planned_reads, Source(plan, image.source));
}
}
if (capture_indirect_reads || !plan.source_reads.empty())
plan.resource_tracking_complete &= !program.has_address_writes;
CompileSrtWalker(plan);
if (plan.srt_plan_complete && plan.resource_tracking_complete) CompileSrtWalker(plan);
return plan;
}
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
ResourceSnapshot& snapshot, ResourceSpecialization& specialization) {
for (auto& keys: program.selector_keys) keys.resolved = false;
if (!program.resource_tracking_complete ||
((program.requires_specialization_memory || !program.source_reads.empty()) &&
runtime.read_specialization_memory == nullptr)) {
return false;
}
if (!program.resource_tracking_complete) return false;
const auto uses_workgroup_count = [](const DescriptorSource& source) {
return source.indirect_descriptor && source.indirect_descriptor->workgroup_axis != UINT32_MAX;
};
@@ -1582,20 +1507,16 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
std::ranges::any_of(program.descriptor_sources, uses_workgroup_count));
auto& reads = snapshot.specialization_reads;
reads = program.source_reads;
SrtReadCapture capture {runtime, reads};
auto observed = capture_reads ? capture.ObservedRuntime() : runtime;
SrtWalker clean(program, CleanRuntime(observed));
SrtWalker walker(program, observed,
capture_reads || program.requires_specialization_memory ? &clean : nullptr);
if (!walker.RefreshFlatBuffer(snapshot.flattened_srt)) {
if (!WalkSrt(program, runtime, snapshot.flattened_srt, capture_reads ? &reads : nullptr)) {
return SpecializationFail(fmt::format("SRT refresh complete={} reads={} user_data={}",
program.srt_plan_complete, program.srt_reads.size(), runtime.user_data.size()));
}
const auto active = std::span<const uint8_t>(program.active_sources);
SrtReadCapture capture {runtime, reads};
auto observed = capture_reads ? capture.ObservedRuntime() : runtime;
std::array<uint32_t, 3> workgroup_counts {};
if (runtime.indirect_args_address != 0u) {
for (uint32_t source = 0; source < program.descriptor_sources.size(); ++source) {
if ((!active.empty() && !active[source]) ||
if (!WalkedActive(program, source) ||
!uses_workgroup_count(program.descriptor_sources[source])) continue;
if ((runtime.indirect_args_address & 3u) != 0u ||
!ReadTable(runtime.indirect_args_address, sizeof(workgroup_counts), 0u,
@@ -1610,24 +1531,24 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
std::array<uint32_t, 4> stored {};
bool uniform_fill = words != 0;
if (uniform_fill && !fill.pattern_size.IsEmpty())
uniform_fill = clean.Evaluate(fill.pattern_size, words) && words >= 1 && words <= 4;
uniform_fill = WalkedValue(program, fill.pattern_size, words) && words >= 1 && words <= 4;
uint32_t elements = 0;
if (uniform_fill && !fill.element_count.IsEmpty())
uniform_fill = clean.Evaluate(fill.element_count, elements) && elements != 0;
uniform_fill = WalkedValue(program, fill.element_count, elements) && elements != 0;
for (uint32_t i = 0; i < words && uniform_fill; ++i) {
uniform_fill = clean.Evaluate(fill.values[i], stored[i]) && stored[i] == stored[0];
uniform_fill = WalkedValue(program, fill.values[i], stored[i]) && stored[i] == stored[0];
}
if (uniform_fill) {
snapshot.uniform_fill = fill.fill;
snapshot.uniform_fill.value = stored[0];
snapshot.uniform_fill.bounded_elements = elements;
}
const auto evaluate = [&](uint32_t source, DescriptorValue& value, bool written = false) {
const auto evaluate = [&](uint32_t source, DescriptorValue& value) {
if (source >= program.descriptor_sources.size()) {
return false;
}
if (active.empty() || active[source]) {
return (capture_reads && written ? clean : walker).EvaluateDescriptor(source, value) ||
if (WalkedActive(program, source)) {
return WalkedDescriptor(program, source, value) ||
SpecializationFail(fmt::format("descriptor evaluation source={}", source));
}
value = {};
@@ -1651,9 +1572,9 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
if (source == nullptr) return false;
if (source->indirect_descriptor.has_value()) {
snapshot.buffers[i] = {.dword_count = 4u};
if (active.empty() || active[base.source]) {
if (WalkedActive(program, base.source)) {
if (!MaterializeIndirectDescriptor(
program, base.source, i, 4u, 4u, observed, clean, snapshot,
program, base.source, i, 4u, 4u, observed, snapshot,
snapshot.buffers, specialization.buffers,
[&](DescriptorValue& value) {
return NormalizeIndirectBuffer(
@@ -1661,7 +1582,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
}))
return resource_failed("buffer", i, base.source, snapshot.buffers.size());
}
} else if (!evaluate(base.source, snapshot.buffers[i], base.written)) {
} else if (!evaluate(base.source, snapshot.buffers[i])) {
return false;
}
}
@@ -1711,13 +1632,13 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
}
if (source->indirect_descriptor.has_value()) {
snapshot.images[i] = {.dword_count = 8u};
if (!active.empty() && !active[image.source]) {
if (!WalkedActive(program, image.source)) {
continue;
}
const auto& indirect = *source->indirect_descriptor;
const bool bounded_table = IsBoundedDescriptorTable(program, indirect);
if (!MaterializeIndirectDescriptor(
program, image.source, i, 8u, image.r128 ? 4u : 8u, observed, clean, snapshot, snapshot.images,
program, image.source, i, 8u, image.r128 ? 4u : 8u, observed, snapshot, snapshot.images,
specialization.images, [&](DescriptorValue& value) {
// A broad heap also contains resources for other typed image operations.
if (NullImageDescriptor(value) || !ValidImageDescriptor(value, image.r128) ||
@@ -1728,7 +1649,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
return resource_failed("image", i, image.source, snapshot.images.size());
}
} else {
if (!evaluate(image.source, snapshot.images[i], image.written)) {
if (!evaluate(image.source, snapshot.images[i])) {
return false;
}
if (!ValidImageDescriptor(snapshot.images[i], image.r128)) {
@@ -1749,8 +1670,8 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
const auto* indirect = descriptor->indirect_descriptor ? &*descriptor->indirect_descriptor : nullptr;
if (indirect != nullptr && indirect->sources.empty()) {
snapshot.samplers[i] = {.dword_count = 4u};
if ((active.empty() || active[source]) && !MaterializeIndirectDescriptor(
program, source, i, 4u, 4u, observed, clean, snapshot, snapshot.samplers,
if (WalkedActive(program, source) && !MaterializeIndirectDescriptor(
program, source, i, 4u, 4u, observed, snapshot, snapshot.samplers,
specialization.samplers,
[](DescriptorValue&) { return true; }))
return resource_failed("sampler", i, source, snapshot.samplers.size());
File diff suppressed because it is too large Load Diff
@@ -8,8 +8,6 @@
namespace Libs::Graphics::ShaderRecompiler::IR {
class Value;
using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, std::span<uint32_t> values);
// Image descriptors at base + first + k * step, k < count, within size bytes that a sampled
@@ -42,7 +40,6 @@ protected:
struct SrtRuntime {
std::span<const uint32_t> user_data;
uint64_t shader_base = 0;
SrtMemoryReader read_memory = nullptr;
void* userdata = nullptr;
SrtMemoryReader read_specialization_memory = nullptr;
std::span<const uint32_t> workgroup_counts;
@@ -57,20 +54,12 @@ public:
SrtRuntime ObservedRuntime();
private:
static bool ReadStrict(void* userdata, uint64_t address, std::span<uint32_t> values);
static bool ReadOrdinary(void* userdata, uint64_t address, std::span<uint32_t> values);
static bool Read(void* userdata, uint64_t address, std::span<uint32_t> values);
SrtRuntime m_source;
std::vector<std::pair<uint64_t, uint64_t>>& m_ranges;
};
// 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, ImmutableInteger };
bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
@@ -78,12 +67,20 @@ bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
// Uniform across a compute wave, including values derived from its workgroup ID.
// This does not imply that the host can evaluate the value.
bool ValidateWaveUniformValue(const ResourcePlan& program, Value value);
void CompileSrtWalker(ResourcePlan& program);
// Compiles the plan's x86 walker. Without values it walks the plan's resources; otherwise it
// only evaluates the values, with every block reachable.
void CompileSrtWalker(ResourcePlan& plan, std::span<const Value> values = {});
// Runs the walker. flat receives the scalar reads of reachable blocks and reads, when non-null,
// one DWORD range per guest load. False when a reachable read is unknown.
bool WalkSrt(const ResourcePlan& plan, const SrtRuntime& runtime, std::vector<uint32_t>& flat,
std::vector<std::pair<uint64_t, uint64_t>>* reads);
// Results of the last walk; false when unknown.
bool WalkedValue(const ResourcePlan& plan, Value value, uint32_t& result);
bool WalkedDescriptor(const ResourcePlan& plan, uint32_t source, DescriptorValue& result);
bool WalkedActive(const ResourcePlan& plan, uint32_t source);
// Length of the guest load at rip if it belongs to the walker running on this thread, else 0.
// The exception handler completes such a load as a zero read.
uint32_t SrtWalkerLoadLength(uint64_t rip);
// Uses the strict reader for values that affect shader specialization.
SrtRuntime CleanRuntime(SrtRuntime runtime);
// Nonnegative affine coefficients: element zero is constant, remaining elements are
// caller-selected leaves. Runtime range proofs must additionally exclude U32 wrap.
@@ -127,43 +124,6 @@ std::optional<std::array<uint64_t, Size>> AffineIndex(
return left;
}
// One memoized evaluation session shared by the entire shader resource refresh.
class SrtWalker {
public:
SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
SrtWalker* clean_evaluator = nullptr);
~SrtWalker();
SrtWalker(const SrtWalker&) = delete;
SrtWalker& operator=(const SrtWalker&) = delete;
bool Evaluate(Value value, uint32_t& result);
bool EvaluateDescriptor(uint32_t source, DescriptorValue& result);
// Refreshes reachable scalar reads and active descriptor sources in one walk.
bool RefreshFlatBuffer(std::vector<uint32_t>& flat);
private:
SrtWalker(SrtWalker& parent, Value active_mask, bool clean = false,
SrtWalker* clean_evaluator = nullptr);
ResourcePlan::EvaluationContext& AcquireContext();
void ResetUnknowns();
static float Float32(uint64_t bits);
bool EvaluateWide(Value value, uint64_t& result);
bool Arg(const Inst& inst, size_t index, uint64_t& result);
bool EvaluatePhi(const Inst& inst, uint64_t& result);
bool EvaluateExtract(const Inst& inst, uint64_t& result);
bool EvaluateRawRead(const Inst& inst, uint64_t& result);
bool EvaluateInst(const Inst& inst, uint64_t& result);
const ResourcePlan& m_program;
SrtRuntime m_runtime;
SrtWalker* m_clean_evaluator = nullptr;
Value m_active_mask;
const std::vector<uint8_t>* m_reachable_blocks = nullptr;
const SrtWalker* m_session;
uint8_t m_read_policy;
ResourcePlan::EvaluationContext& m_context;
};
} // namespace Libs::Graphics::ShaderRecompiler::IR
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SRTWALKER_H_ */