mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
shader: prove inactive resources across masked traversal merges
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Xbyak {
|
||||
@@ -613,6 +614,7 @@ struct ResourcePlan {
|
||||
std::vector<MemoryInfo> memory_info;
|
||||
std::vector<DescriptorSource> descriptor_sources;
|
||||
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;
|
||||
@@ -628,6 +630,7 @@ struct ResourcePlan {
|
||||
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;
|
||||
mutable std::vector<uint32_t> pending_blocks;
|
||||
mutable std::vector<uint32_t> material_keys;
|
||||
};
|
||||
|
||||
@@ -1122,49 +1122,79 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
for (const auto& read: program.srt_reads) {
|
||||
plan.srt_reads.push_back({Clone(read.value), read.flat_offset});
|
||||
}
|
||||
// A proven uniform factor can decide a branch even when its other lanes are unknown.
|
||||
// Keep only that Boolean structure, never the varying shader dependency graph.
|
||||
Value unknown;
|
||||
// Retain integer control expressions with opaque varying leaves. Their identities
|
||||
// matter when an EXEC factor also guards a selected value; never clone float work.
|
||||
bool capture_control_reads = false;
|
||||
std::unordered_map<const Inst*, Value> predicates;
|
||||
bool uniform_control = false;
|
||||
const auto control_value_count = plan.value_storage.size();
|
||||
struct ControlValue {
|
||||
Value value;
|
||||
bool complete = false;
|
||||
};
|
||||
std::unordered_map<const Inst*, ControlValue> predicates;
|
||||
std::function<Value(Value)> ClonePredicate = [&](Value value) -> Value {
|
||||
value = value.Resolve();
|
||||
if (value.IsEmpty()) return {};
|
||||
const auto* inst = value.TryInstruction();
|
||||
if (inst == nullptr)
|
||||
if (inst == nullptr) {
|
||||
uniform_control |= value.GetType() == Type::U1;
|
||||
return ValidateRuntimeValue(program, value, RuntimeValueType::Integer) ? value : Value {};
|
||||
// Preserve shared EXEC predicates, including subgraphs with no uniform factor.
|
||||
}
|
||||
const auto [found, inserted] = predicates.try_emplace(inst);
|
||||
if (!inserted) return found->second;
|
||||
auto& result = found->second;
|
||||
auto& entry = found->second;
|
||||
auto& result = entry.value;
|
||||
const auto opaque = [&]() -> Value {
|
||||
if (value.GetType() != Type::U1 && value.GetType() != Type::U32) return {};
|
||||
return result = Value(&plan.value_storage.emplace_back(
|
||||
value.GetType() == Type::U1 ? ValueOpcode::UndefU1 : ValueOpcode::UndefU32));
|
||||
};
|
||||
// A recursive projection remains opaque, including unstructured loop Phis.
|
||||
if (!inserted) return entry.complete || !result.IsEmpty() ? result : opaque();
|
||||
const auto finish = [&](Value projected) {
|
||||
entry.complete = true;
|
||||
return result = projected;
|
||||
};
|
||||
if (ValidateRuntimeValue(program, value, RuntimeValueType::Integer)) {
|
||||
uniform_control = true;
|
||||
capture_control_reads |=
|
||||
!ValidateRuntimeValue(program, value, RuntimeValueType::ImmutableInteger);
|
||||
return result = Clone(value);
|
||||
return finish(Clone(value));
|
||||
}
|
||||
const auto op = inst->GetOpcode();
|
||||
if (op == ValueOpcode::ConditionRef || op == ValueOpcode::LogicalNot) {
|
||||
const auto operand = ClonePredicate(inst->Arg(0));
|
||||
if (operand.IsEmpty() || op == ValueOpcode::ConditionRef) return result = operand;
|
||||
auto& node = plan.value_storage.emplace_back(op);
|
||||
node.SetArg(0, operand);
|
||||
return result = Value(&node);
|
||||
}
|
||||
if (op != ValueOpcode::LogicalAnd && op != ValueOpcode::LogicalOr) return {};
|
||||
auto left = ClonePredicate(inst->Arg(0));
|
||||
auto right = ClonePredicate(inst->Arg(1));
|
||||
if (left.IsEmpty() && right.IsEmpty()) return {};
|
||||
if (left.IsEmpty() || right.IsEmpty()) {
|
||||
if (unknown.IsEmpty()) unknown = Value(&plan.value_storage.emplace_back(ValueOpcode::UndefU1));
|
||||
if (left.IsEmpty()) left = unknown;
|
||||
if (right.IsEmpty()) right = unknown;
|
||||
if (op == ValueOpcode::ConditionRef) return finish(ClonePredicate(inst->Arg(0)));
|
||||
if (op == ValueOpcode::Phi) {
|
||||
if (inst->Parent() == nullptr || inst->Parent()->terminator.loop_header ||
|
||||
inst->NumPhiBlocks() != inst->NumArgs() || inst->NumArgs() == 0u ||
|
||||
(value.GetType() != Type::U1 && value.GetType() != Type::U32)) return finish(opaque());
|
||||
std::vector<uint32_t> predecessors;
|
||||
for (size_t index = 0; index < inst->NumArgs(); ++index) {
|
||||
const auto predecessor = std::ranges::find(program.blocks, inst->PhiBlock(index));
|
||||
if (predecessor == program.blocks.end()) return finish(opaque());
|
||||
predecessors.push_back(static_cast<uint32_t>(predecessor - program.blocks.begin()));
|
||||
}
|
||||
auto& node = plan.value_storage.emplace_back(op, inst->Flags<uint64_t>());
|
||||
plan.control_phi_predecessors.emplace(&node, std::move(predecessors));
|
||||
for (size_t index = 0; index < inst->NumArgs(); ++index)
|
||||
node.AddPhiOperand(nullptr, ClonePredicate(inst->Arg(index)));
|
||||
return finish(Value(&node));
|
||||
}
|
||||
if (op != ValueOpcode::LogicalAnd && op != ValueOpcode::LogicalOr &&
|
||||
op != ValueOpcode::LogicalNot && op != ValueOpcode::IEqual32 &&
|
||||
op != ValueOpcode::INotEqual32 && op != ValueOpcode::ULessThanEqual32 &&
|
||||
op != ValueOpcode::SelectU1 && op != ValueOpcode::SelectU32) return finish(opaque());
|
||||
auto& node = plan.value_storage.emplace_back(op);
|
||||
node.SetArg(0, left);
|
||||
node.SetArg(1, right);
|
||||
return result = Value(&node);
|
||||
for (size_t index = 0; index < inst->NumArgs(); ++index)
|
||||
node.SetArg(index, ClonePredicate(inst->Arg(index)));
|
||||
return finish(Value(&node));
|
||||
};
|
||||
plan.control_flow = ResourceControlFlow(program, ClonePredicate);
|
||||
if (!uniform_control) {
|
||||
plan.control_flow.clear();
|
||||
plan.control_phi_predecessors.clear();
|
||||
auto first = std::next(plan.value_storage.begin(), control_value_count);
|
||||
for (auto it = first; it != plan.value_storage.end(); ++it) it->Invalidate();
|
||||
plan.value_storage.erase(first, plan.value_storage.end());
|
||||
}
|
||||
plan.uniform_fill = AnalyzeUniformFill(program);
|
||||
for (uint32_t i = 0; i < plan.uniform_fill.fill.words; ++i) {
|
||||
plan.uniform_fill.values[i] = Clone(plan.uniform_fill.values[i]);
|
||||
|
||||
@@ -585,6 +585,19 @@ bool SrtWalker::Arg(const Inst& inst, size_t index, uint64_t& result) {
|
||||
}
|
||||
|
||||
bool SrtWalker::EvaluatePhi(const Inst& inst, uint64_t& result) {
|
||||
if (const auto control = m_program.control_phi_predecessors.find(&inst);
|
||||
control != m_program.control_phi_predecessors.end()) {
|
||||
bool found = false;
|
||||
for (size_t index = 0; index < inst.NumArgs(); ++index) {
|
||||
if (m_reachable_blocks != nullptr &&
|
||||
!m_reachable_blocks->at(control->second[index])) continue;
|
||||
uint64_t incoming = 0;
|
||||
if (!Arg(inst, index, incoming) || (found && incoming != result)) return false;
|
||||
result = incoming;
|
||||
found = true;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
const auto value = ResolveInvariantPhi(m_program, Value(const_cast<Inst*>(&inst)));
|
||||
return !value.IsEmpty() && EvaluateWide(value, result);
|
||||
}
|
||||
@@ -1019,7 +1032,13 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
|
||||
}
|
||||
return false;
|
||||
case ValueOpcode::ULessThanEqual32:
|
||||
if (binary()) {
|
||||
// A zero lower bound accepts every U32, including a varying lane value.
|
||||
if (!Arg(inst, 0, a)) return false;
|
||||
if (a == 0u) {
|
||||
result = 1u;
|
||||
return true;
|
||||
}
|
||||
if (Arg(inst, 1, b)) {
|
||||
result = static_cast<uint32_t>(a) <= static_cast<uint32_t>(b);
|
||||
return true;
|
||||
}
|
||||
@@ -1043,9 +1062,22 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
|
||||
result = 0u;
|
||||
return true;
|
||||
}
|
||||
if (!Arg(inst, 1, b) || (b != 0u && !left)) return false;
|
||||
result = b != 0u;
|
||||
return true;
|
||||
if (Arg(inst, 1, b) && (b == 0u || left)) {
|
||||
result = b != 0u;
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
if (active.EvaluateWide(inst.Arg(1), b) && b == 0u) {
|
||||
result = 0u;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case ValueOpcode::LogicalOr: {
|
||||
const bool left = Arg(inst, 0, a);
|
||||
@@ -1124,35 +1156,48 @@ bool SrtWalker::RefreshFlatBuffer(std::vector<uint32_t>& flat) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
auto& visited = m_program.visited_blocks;
|
||||
auto& reachable = m_program.reachable_blocks;
|
||||
auto& pending = m_program.pending_blocks;
|
||||
visited.assign(m_program.control_flow.size(), 1u);
|
||||
m_reachable_blocks = &visited;
|
||||
if (m_clean_evaluator != nullptr) m_clean_evaluator->m_reachable_blocks = &visited;
|
||||
for (;;) {
|
||||
reachable.assign(m_program.control_flow.size(), 0u);
|
||||
pending.clear();
|
||||
pending.push_back(0u);
|
||||
while (!pending.empty()) {
|
||||
const auto index = pending.back();
|
||||
pending.pop_back();
|
||||
if (reachable.at(index)) continue;
|
||||
reachable[index] = 1u;
|
||||
const auto& block = m_program.control_flow[index];
|
||||
uint32_t condition = 0;
|
||||
auto& predicate = m_clean_evaluator != nullptr ? *m_clean_evaluator : *this;
|
||||
if (!block.condition.IsEmpty() &&
|
||||
(m_runtime.read_specialization_memory != nullptr || !m_program.capture_specialization_reads) &&
|
||||
predicate.Evaluate(block.condition, condition)) {
|
||||
pending.push_back(block.successors[condition != 0u ? 0u : 1u]);
|
||||
} else {
|
||||
pending.insert(pending.end(), block.successors.begin(), block.successors.end());
|
||||
}
|
||||
}
|
||||
if (reachable == visited) break;
|
||||
visited.swap(reachable);
|
||||
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.
|
||||
}
|
||||
flat.assign(m_program.srt_reads.size(), 0u);
|
||||
active.assign(m_program.descriptor_sources.size(), 1u);
|
||||
for (const auto& block: m_program.control_flow) {
|
||||
for (const auto& block: m_program.control_flow)
|
||||
for (const auto source: block.sources) active.at(source) = 0u;
|
||||
}
|
||||
auto& visited = m_program.visited_blocks;
|
||||
auto& pending = m_program.pending_blocks;
|
||||
visited.assign(m_program.control_flow.size(), 0u);
|
||||
pending.clear();
|
||||
pending.push_back(0u);
|
||||
while (!pending.empty()) {
|
||||
const auto index = pending.back();
|
||||
pending.pop_back();
|
||||
if (visited.at(index)) continue;
|
||||
visited[index] = 1u;
|
||||
for (uint32_t index = 0; index < visited.size(); ++index) {
|
||||
if (!visited[index]) continue;
|
||||
const auto& block = m_program.control_flow[index];
|
||||
for (const auto source: block.sources) active[source] = 1u;
|
||||
for (const auto slot: block.srt_reads) {
|
||||
for (const auto slot: block.srt_reads)
|
||||
if (!refresh(slot)) return false;
|
||||
}
|
||||
uint32_t condition = 0;
|
||||
auto& predicate = m_clean_evaluator != nullptr ? *m_clean_evaluator : *this;
|
||||
if (!block.condition.IsEmpty() &&
|
||||
(m_runtime.read_specialization_memory != nullptr || !m_program.capture_specialization_reads) &&
|
||||
predicate.Evaluate(block.condition, condition)) {
|
||||
pending.push_back(block.successors[condition != 0u ? 0u : 1u]);
|
||||
} else {
|
||||
pending.insert(pending.end(), block.successors.begin(), block.successors.end());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ private:
|
||||
SrtRuntime m_runtime;
|
||||
SrtWalker* m_clean_evaluator = nullptr;
|
||||
Value m_active_mask;
|
||||
const std::vector<uint8_t>* m_reachable_blocks = nullptr;
|
||||
ResourcePlan::EvaluationContext& m_context;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user