From f6fbb480c5f8050c3e17ac9090a3724426b72a5b Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Sat, 10 Oct 2026 00:03:58 +0200 Subject: [PATCH] perf(recompiler): preserve sorted dominance sets during insertion Both dominance analyses start from sorted unique sets and intersect them. Insert the current block ID with lower_bound instead of sorting again. Preserve every intermediate set and the existing ID-order iteration. Keep the generic unsorted helper and all graph rewrite logic unchanged. Compare both analyses against the original algorithms on six raw CFGs. Cover finite loops, closed SCCs, unreachable islands and permuted IDs. --- .../libSceAgcDriver/tests/Structurizer.cpp | 111 ++++++++++++++++++ .../ControlFlow/src/Structurizer.cpp | 13 +- 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp b/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp index c9771f1fe..d6dfb7861 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -103,6 +104,108 @@ void requireExactPostDominators(const ControlFlowGraph& graph, const char* name) } } +std::vector> originalPostDominators(const ControlFlowGraph& graph) { + std::vector all; + for (std::uint32_t id = 0; id < graph.blocks.size(); ++id) all.push_back(id); + std::vector> result; + for (const auto& block : graph.blocks) result.push_back(block.successors.empty() ? std::vector{block.id} : all); + bool changed = true; + while (changed) { + changed = false; + for (const auto& block : graph.blocks) { + std::vector next; + if (block.successors.empty()) { + next = {block.id}; + } else { + next = result[block.successors.front()]; + for (std::size_t index = 1; index < block.successors.size(); ++index) { + std::vector intersection; + const auto& successor = result[block.successors[index]]; + std::set_intersection(next.begin(), next.end(), successor.begin(), successor.end(), std::back_inserter(intersection)); + next = std::move(intersection); + } + if (std::find(next.begin(), next.end(), block.id) == next.end()) next.push_back(block.id); + std::sort(next.begin(), next.end()); + next.erase(std::unique(next.begin(), next.end()), next.end()); + } + if (next != result[block.id]) { + result[block.id] = std::move(next); + changed = true; + } + } + } + return result; +} + +std::vector> originalDominators(const ControlFlowGraph& graph) { + std::vector all; + for (std::uint32_t id = 0; id < graph.blocks.size(); ++id) all.push_back(id); + std::vector> result; + for (const auto& block : graph.blocks) result.push_back(block.id == graph.entryBlock ? std::vector{block.id} : all); + bool changed = true; + while (changed) { + changed = false; + for (const auto& block : graph.blocks) { + if (block.id == graph.entryBlock) continue; + std::vector next; + if (block.predecessors.empty()) { + next = {block.id}; + } else { + next = result[block.predecessors.front()]; + for (std::size_t index = 1; index < block.predecessors.size(); ++index) { + std::vector intersection; + const auto& predecessor = result[block.predecessors[index]]; + std::set_intersection(next.begin(), next.end(), predecessor.begin(), predecessor.end(), std::back_inserter(intersection)); + next = std::move(intersection); + } + if (std::find(next.begin(), next.end(), block.id) == next.end()) next.push_back(block.id); + std::sort(next.begin(), next.end()); + next.erase(std::unique(next.begin(), next.end()), next.end()); + } + if (next != result[block.id]) { + result[block.id] = std::move(next); + changed = true; + } + } + } + return result; +} + +void requireOriginalDominanceSets(ControlFlowGraph graph, const char* name) { + for (auto& block : graph.blocks) { + block.dominators.clear(); + block.postDominators.clear(); + } + const auto expected = originalPostDominators(graph); + const auto expectedDominators = originalDominators(graph); + const auto original = graph; + const std::string stopAfterAnalyses = "dominance differential fixture: stop before CFG rewrites"; + graph.unsupported = true; + graph.unsupportedReason = stopAfterAnalyses; + bool stopped = false; + try { + Structurizer{}.Structurize(graph); + } catch (const std::runtime_error& error) { + if (error.what() != stopAfterAnalyses) throw; + stopped = true; + } + if (!stopped || graph.blocks.size() != original.blocks.size() || graph.entryBlock != original.entryBlock) { + throw std::runtime_error(std::string(name) + ": differential fixture did not stop before CFG rewrites"); + } + for (std::size_t id = 0; id < graph.blocks.size(); ++id) { + const auto& block = graph.blocks[id]; + if (block.id != original.blocks[id].id || block.successors != original.blocks[id].successors || block.predecessors != original.blocks[id].predecessors) { + throw std::runtime_error(std::string(name) + ": differential fixture changed the CFG"); + } + if (block.postDominators != expected[id]) { + throw std::runtime_error(std::string(name) + ": block " + std::to_string(id) + " differs from the original post-dominator fixed point"); + } + if (block.dominators != expectedDominators[id]) { + throw std::runtime_error(std::string(name) + ": block " + std::to_string(id) + " differs from the original dominator fixed point"); + } + } +} + } int main() { @@ -165,6 +268,14 @@ int main() { } Structurizer{}.Structurize(sharedDiscard); requireStructuredBranches(sharedDiscard, "early discards that share one ending block"); + requireOriginalDominanceSets(makeGraph(diamonds), "raw diamond chain"); + requireOriginalDominanceSets(makeGraph({{1}, {2, 3}, {1}, {}}), "a loop with an exit"); + requireOriginalDominanceSets(makeGraph({{1}, {2}, {1}}), "a non-exiting SCC"); + requireOriginalDominanceSets(makeGraph({{1, 3}, {2}, {1}, {}}), "an exit-or-spin branch"); + requireOriginalDominanceSets(makeGraph({{1}, {}, {3}, {}, {5}, {4}}), "unreachable exit and non-exiting islands"); + auto reordered = makeGraph({{1}, {}, {2}, {0, 4}, {1}}); + reordered.entryBlock = 3; + requireOriginalDominanceSets(std::move(reordered), "a nonzero entry with permuted block IDs"); } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); return 1; diff --git a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp index 2401a6290..0b59967fe 100644 --- a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp +++ b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp @@ -20,6 +20,13 @@ std::vector allBlockIds(std::uint32_t count) { return ids; } +void addSortedUnique(std::vector& values, std::uint32_t value) { + const auto position = std::lower_bound(values.begin(), values.end(), value); + if (position == values.end() || *position != value) { + values.insert(position, value); + } +} + bool replaceValue(std::vector& values, std::uint32_t oldValue, std::uint32_t newValue) { bool changed = false; for (auto& value : values) { @@ -1330,8 +1337,7 @@ void Structurizer::computeDominatorTree(ControlFlowGraph& graph) const { for (std::size_t i = 1; i < block.predecessors.size(); ++i) { next = intersectSorted(next, graph.blocks[block.predecessors[i]].dominators); } - addUnique(next, block.id); - sortUnique(next); + addSortedUnique(next, block.id); } if (next != block.dominators) { @@ -1412,8 +1418,7 @@ void Structurizer::computePostDominators(ControlFlowGraph& graph) const { for (std::size_t i = 1; i < block.successors.size(); ++i) { next = intersectSorted(next, graph.blocks[block.successors[i]].postDominators); } - addUnique(next, block.id); - sortUnique(next); + addSortedUnique(next, block.id); } if (next != block.postDominators) {