shader: reuse EXEC proof contexts across shared predicate graphs

This commit is contained in:
nmzik
2026-10-13 12:36:32 +02:00
parent 3197ef093c
commit e998f21e71
4 changed files with 147 additions and 24 deletions
+5 -1
View File
@@ -28,6 +28,8 @@ 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;
@@ -596,6 +598,9 @@ struct ResourcePlan {
std::vector<Entry> values;
uint64_t generation = 0;
const SrtWalker* session = nullptr;
Value active_mask;
uint8_t read_policy = 0;
};
ResourcePlan();
@@ -627,7 +632,6 @@ struct ResourcePlan {
// 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 uint32_t evaluation_depth = 0;
mutable std::vector<uint8_t> active_sources;
mutable std::vector<uint8_t> visited_blocks;
mutable std::vector<uint8_t> reachable_blocks;
@@ -499,12 +499,24 @@ void CompileSrtWalker(ResourcePlan& program) {
}
SrtWalker::SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
SrtWalker* clean_evaluator, Value active_mask)
SrtWalker* clean_evaluator)
: m_program(program), m_runtime(runtime), m_clean_evaluator(clean_evaluator),
m_active_mask(active_mask.Resolve()),
m_context(AcquireContext(program)) {}
m_session(this),
m_read_policy(clean_evaluator != nullptr ? 2u : 0u), m_context(AcquireContext()) {}
SrtWalker::~SrtWalker() { --m_program.evaluation_depth; }
SrtWalker::SrtWalker(SrtWalker& parent, Value active_mask, bool clean,
SrtWalker* clean_evaluator)
: m_program(parent.m_program), m_runtime(clean ? CleanRuntime(parent.m_runtime) : parent.m_runtime),
m_clean_evaluator(clean_evaluator), m_active_mask(active_mask.Resolve()),
m_reachable_blocks(parent.m_reachable_blocks), m_session(parent.m_session),
m_read_policy((parent.m_read_policy & 1u) | (clean ? 1u : 0u) |
(clean_evaluator != nullptr ? 2u : 0u)), m_context(AcquireContext()) {}
SrtWalker::~SrtWalker() {
if (m_session != this) return;
for (auto& context: m_program.evaluation_contexts)
if (context.session == this) context.session = nullptr;
}
bool SrtWalker::Evaluate(Value value, uint32_t& result) {
uint64_t wide = 0;
@@ -515,15 +527,29 @@ bool SrtWalker::Evaluate(Value value, uint32_t& result) {
return true;
}
ResourcePlan::EvaluationContext& SrtWalker::AcquireContext(const ResourcePlan& program) {
if (program.evaluation_depth == program.evaluation_contexts.size()) {
program.evaluation_contexts.emplace_back();
ResourcePlan::EvaluationContext& SrtWalker::AcquireContext() {
ResourcePlan::EvaluationContext* unused = nullptr;
for (auto& context: m_program.evaluation_contexts) {
if (context.session == m_session && context.active_mask == m_active_mask &&
context.read_policy == m_read_policy) return context;
if (context.session == nullptr && unused == nullptr) unused = &context;
}
auto& context = program.evaluation_contexts[program.evaluation_depth++];
context.generation += 2;
auto& context = unused != nullptr ? *unused : m_program.evaluation_contexts.emplace_back();
context.session = m_session;
context.active_mask = m_active_mask;
context.read_policy = m_read_policy;
context.generation += 4;
return context;
}
void SrtWalker::ResetUnknowns() {
for (auto& context: m_program.evaluation_contexts) {
if (context.session != m_session) continue;
for (auto& entry: context.values)
if (entry.generation == (context.generation | 2u)) entry.generation = 0;
}
}
float SrtWalker::Float32(uint64_t bits) {
return std::bit_cast<float>(static_cast<uint32_t>(bits));
}
@@ -561,8 +587,10 @@ bool SrtWalker::EvaluateWide(Value value, uint64_t& result) {
result = m_context.values[index].value;
return true;
}
// The low generation bit marks an instruction that is still being evaluated.
if (m_context.values[index].generation == (m_context.generation | 1u)) {
// Visiting and completed-unknown entries both stop repeated evaluation in this
// context. Only completed unknowns are retried when reachability becomes tighter.
if (m_context.values[index].generation == (m_context.generation | 1u) ||
m_context.values[index].generation == (m_context.generation | 2u)) {
return false;
}
m_context.values[index].generation = m_context.generation | 1u;
@@ -571,7 +599,7 @@ bool SrtWalker::EvaluateWide(Value value, uint64_t& result) {
// Recursive evaluation may grow the dense memo vector.
auto& memo = m_context.values[index];
if (!evaluated) {
memo.generation = 0;
memo.generation = m_context.generation | 2u;
return false;
}
memo.value = out;
@@ -744,9 +772,8 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
if (!m_active_mask.IsEmpty() && inst.Arg(1).Resolve() == m_active_mask) {
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, &clean_active, inst.Arg(1));
SrtWalker clean_active(*this, inst.Arg(1), true);
SrtWalker active(*this, inst.Arg(1), false, &clean_active);
return active.EvaluateWide(inst.Arg(0), result);
}
case ValueOpcode::BitCastU32F32:
@@ -1070,9 +1097,8 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
if (!left) {
// Only lanes satisfying the left EXEC factor can make the conjunction
// true. Use a separate memo so this assumption cannot escape its RHS.
SrtWalker active(m_program, m_clean_evaluator != nullptr
? CleanRuntime(m_runtime) : m_runtime, nullptr, inst.Arg(0).Resolve());
active.m_reachable_blocks = m_reachable_blocks;
SrtWalker active(m_clean_evaluator != nullptr ? *m_clean_evaluator : *this,
inst.Arg(0));
if (active.EvaluateWide(inst.Arg(1), b) && b == 0u) {
result = 0u;
return true;
@@ -1161,8 +1187,12 @@ bool SrtWalker::RefreshFlatBuffer(std::vector<uint32_t>& flat) {
auto& reachable = m_program.reachable_blocks;
auto& pending = m_program.pending_blocks;
visited.assign(m_program.control_flow.size(), 1u);
ResetUnknowns();
m_reachable_blocks = &visited;
if (m_clean_evaluator != nullptr) m_clean_evaluator->m_reachable_blocks = &visited;
if (m_clean_evaluator != nullptr) {
m_clean_evaluator->ResetUnknowns();
m_clean_evaluator->m_reachable_blocks = &visited;
}
for (;;) {
reachable.assign(m_program.control_flow.size(), 0u);
pending.clear();
@@ -1188,6 +1218,8 @@ bool SrtWalker::RefreshFlatBuffer(std::vector<uint32_t>& flat) {
if (m_program.control_phi_predecessors.empty()) break;
// Only unknown values need another evaluation: successful Phi facts remain
// true as their possible predecessors shrink. Keep uniform-read memos stable.
ResetUnknowns();
if (m_clean_evaluator != nullptr) m_clean_evaluator->ResetUnknowns();
}
flat.assign(m_program.srt_reads.size(), 0u);
active.assign(m_program.descriptor_sources.size(), 1u);
@@ -99,7 +99,7 @@ std::optional<std::array<uint64_t, Size>> AffineIndex(
class SrtWalker {
public:
SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
SrtWalker* clean_evaluator = nullptr, Value active_mask = {});
SrtWalker* clean_evaluator = nullptr);
~SrtWalker();
SrtWalker(const SrtWalker&) = delete;
SrtWalker& operator=(const SrtWalker&) = delete;
@@ -110,7 +110,10 @@ public:
bool RefreshFlatBuffer(std::vector<uint32_t>& flat);
private:
static ResourcePlan::EvaluationContext& AcquireContext(const ResourcePlan& program);
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);
@@ -124,6 +127,8 @@ private:
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;
};