Merge pull request #4364 from SleglJan/fix/structurizer-return-join

fix(recompiler): give an early-return selection a merge when its parent's other arm reaches the same join
This commit is contained in:
Dean 2026-10-13 07:33:30 +13:00 committed by GitHub
commit 5d933db02e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 59 additions and 13 deletions

View File

@ -1119,6 +1119,29 @@ early_exit:
{0x7e020280u, 0xbf06c002u, 0xbf850003u, 0x4a020081u, 0xbf068003u, 0xbf850006u, 0x7d880088u, 0xbf870001u, 0x4a020282u, 0xe0700000u,
0x80000100u, 0xbf810000u, 0xbefe0480u, 0xbf810000u},
Split::None},
{"early exit arm beside a parent arm that reaches the same merge", R"(
v_mov_b32 v1, 0
s_cmp_eq_u32 s2, 64
s_cbranch_scc1 other
v_add_nc_u32 v1, 1, v0
s_cmp_eq_u32 s3, 0
s_cbranch_scc1 early_exit
join:
v_cmp_gt_u32 vcc, 8, v0
s_cbranch_vccnz store
v_add_nc_u32 v1, 2, v1
store:
buffer_store_dword v1, off, s[0:3], 0
s_endpgm
other:
v_add_nc_u32 v1, 3, v0
s_branch join
early_exit:
s_mov_b64 exec, 0
s_endpgm)",
{0x7e020280u, 0xbf06c002u, 0xbf850009u, 0x4a020081u, 0xbf068003u, 0xbf850008u, 0x7d880088u, 0xbf870001u, 0x4a020282u, 0xe0700000u,
0x80000100u, 0xbf810000u, 0x4a020083u, 0xbf82fff8u, 0xbefe0480u, 0xbf810000u},
Split::None},
{"return block entered from the return block after it", R"(
v_mov_b32 v1, 0
s_cmp_eq_u32 s2, 64

View File

@ -328,6 +328,13 @@ int main() {
requireExactPostDominators(earlyReturn, "an early return inside a selection that joins its parent's merge");
requireStructuredBranches(earlyReturn, "an early return inside a selection that joins its parent's merge");
requireDominatedMerges(earlyReturn, "an early return inside a selection that joins its parent's merge");
const std::vector<std::vector<std::uint32_t>> elseReturnSuccessors{{1, 2}, {4}, {3, 4}, {}, {5, 6}, {7}, {7}, {}};
auto elseReturn = makeGraph(elseReturnSuccessors);
Structurizer{}.Structurize(elseReturn);
requireExactPostDominators(elseReturn, "an early return in one arm of a selection whose other arm reaches the same merge");
requireStructuredBranches(elseReturn, "an early return in one arm of a selection whose other arm reaches the same merge");
requireDominatedMerges(elseReturn, "an early return in one arm of a selection whose other arm reaches the same merge");
requireOriginalBranches(elseReturn, elseReturnSuccessors, "an early return in one arm of a selection whose other arm reaches the same merge");
std::vector<std::vector<std::uint32_t>> diamonds;
for (std::uint32_t diamond = 0; diamond < 50u; ++diamond) {
const auto head = diamond * 3u;

View File

@ -10,7 +10,8 @@ public:
void Structurize(ControlFlowGraph& graph) const;
private:
void structurize(ControlFlowGraph& graph, bool privatizeReturns, bool routeLeavingExits, bool routeSharedExits) const;
void structurizeAttempts(ControlFlowGraph& graph, bool relaxReturnJoins) const;
void structurize(ControlFlowGraph& graph, bool privatizeReturns, bool routeLeavingExits, bool routeSharedExits, bool relaxReturnJoins) const;
void privatizeSharedReturns(ControlFlowGraph& graph) const;
void computeDominatorTree(ControlFlowGraph& graph) const;
void detectNaturalLoops(ControlFlowGraph& graph) const;
@ -19,7 +20,7 @@ private:
void computeStronglyConnectedComponents(ControlFlowGraph& graph) const;
void recomputeAnalyses(ControlFlowGraph& graph) const;
void canonicalizeNaturalLoops(ControlFlowGraph& graph, bool routeLeavingExits, bool routeSharedExits) const;
void splitSharedMergeBlocks(ControlFlowGraph& graph, bool routeLeavingExits, bool routeSharedExits) const;
void splitSharedMergeBlocks(ControlFlowGraph& graph, bool routeLeavingExits, bool routeSharedExits, bool relaxReturnJoins) const;
void isolateSemanticLoopHeaders(ControlFlowGraph& graph) const;
void orderByDominance(ControlFlowGraph& graph) const;
void clearStructuredTerminators(ControlFlowGraph& graph) const;

View File

@ -755,7 +755,7 @@ bool splitSharedMergeBlock(ControlFlowGraph& graph, std::uint32_t merge, const s
return true;
}
bool splitOneReturnJoin(ControlFlowGraph& graph) {
bool splitOneReturnJoin(ControlFlowGraph& graph, bool relaxReturnJoins) {
for (const auto& block : graph.blocks) {
if (block.terminator.kind != TerminatorKind::ConditionalBranch || findInnermostContainingLoop(graph, block.id) != nullptr || findSelectionMerge(graph, block) != InvalidControlFlowId) {
continue;
@ -763,9 +763,9 @@ bool splitOneReturnJoin(ControlFlowGraph& graph) {
const auto returns = [&](std::uint32_t target) { return graph.Dominates(block.id, target) && hasLinearPathToTerminal(graph, target); };
const auto joinsEnclosing = [&](std::uint32_t target) {
const auto& predecessors = graph.FindBlock(target).predecessors;
return !graph.Dominates(block.id, target) && std::all_of(predecessors.begin(), predecessors.end(), [&](std::uint32_t predecessor) {
return !graph.Dominates(block.id, target) && (relaxReturnJoins || std::all_of(predecessors.begin(), predecessors.end(), [&](std::uint32_t predecessor) {
return graph.Dominates(block.id, predecessor) || graph.Dominates(predecessor, block.id);
});
}));
};
const auto trueTarget = block.terminator.trueBlock;
const auto falseTarget = block.terminator.falseBlock;
@ -1455,24 +1455,39 @@ void tarjanVisit(TarjanState& state, std::uint32_t blockId) {
void Structurizer::Structurize(ControlFlowGraph& graph) const {
const auto original = graph;
try {
structurize(graph, false, false, false);
structurizeAttempts(graph, false);
} catch (const std::runtime_error&) {
const auto error = std::current_exception();
graph = original;
try {
structurizeAttempts(graph, true);
} catch (const std::runtime_error&) {
std::rethrow_exception(error);
}
}
}
void Structurizer::structurizeAttempts(ControlFlowGraph& graph, bool relaxReturnJoins) const {
const auto original = graph;
try {
structurize(graph, false, false, false, relaxReturnJoins);
if (!hasStructuredConstructs(graph)) throw std::runtime_error("CFG does not have structured constructs");
} catch (const std::runtime_error&) {
graph = original;
try {
structurize(graph, true, false, false);
structurize(graph, true, false, false, relaxReturnJoins);
if (!hasStructuredConstructs(graph)) throw std::runtime_error("CFG does not have structured constructs");
} catch (const std::runtime_error&) {
const auto error = std::current_exception();
graph = original;
try {
structurize(graph, false, true, false);
structurize(graph, false, true, false, relaxReturnJoins);
if (hasStructuredConstructs(graph)) return;
} catch (const std::runtime_error&) {
}
graph = original;
try {
structurize(graph, false, true, true);
structurize(graph, false, true, true, relaxReturnJoins);
} catch (const std::runtime_error&) {
std::rethrow_exception(error);
}
@ -1494,14 +1509,14 @@ void Structurizer::privatizeSharedReturns(ControlFlowGraph& graph) const {
}
}
void Structurizer::structurize(ControlFlowGraph& graph, bool privatizeReturns, bool routeLeavingExits, bool routeSharedExits) const {
void Structurizer::structurize(ControlFlowGraph& graph, bool privatizeReturns, bool routeLeavingExits, bool routeSharedExits, bool relaxReturnJoins) const {
recomputeAnalyses(graph);
verifyReducibility(graph);
if (privatizeReturns) {
privatizeSharedReturns(graph);
}
canonicalizeNaturalLoops(graph, routeLeavingExits, routeSharedExits);
splitSharedMergeBlocks(graph, routeLeavingExits, routeSharedExits);
splitSharedMergeBlocks(graph, routeLeavingExits, routeSharedExits, relaxReturnJoins);
isolateSemanticLoopHeaders(graph);
orderByDominance(graph);
clearStructuredTerminators(graph);
@ -1795,13 +1810,13 @@ void Structurizer::canonicalizeNaturalLoops(ControlFlowGraph& graph, bool routeL
throw std::runtime_error("CFG loop canonicalization exceeded rewrite budget");
}
void Structurizer::splitSharedMergeBlocks(ControlFlowGraph& graph, bool routeLeavingExits, bool routeSharedExits) const {
void Structurizer::splitSharedMergeBlocks(ControlFlowGraph& graph, bool routeLeavingExits, bool routeSharedExits, bool relaxReturnJoins) const {
const auto originalBlockCount = static_cast<std::uint32_t>(graph.blocks.size());
const auto splitBudget = std::max<std::uint32_t>(16u, originalBlockCount * 4u);
const auto programWords = estimatedSpirvWords(graph, allBlockIds(originalBlockCount));
SplitBudget budget{programWords, std::max(CloneWordLimit, programWords / CloneBudgetDivisor), std::max(CloneWordLimit, programWords), nextGotoVariable(graph)};
for (std::uint32_t splits = 0; splits < splitBudget; ++splits) {
if (!splitOneLoopMerge(graph) && !splitOneSelectionMerge(graph, budget, [this](ControlFlowGraph& candidate) { recomputeAnalyses(candidate); }, routeLeavingExits, routeSharedExits) && !splitOneReturnJoin(graph)) {
if (!splitOneLoopMerge(graph) && !splitOneSelectionMerge(graph, budget, [this](ControlFlowGraph& candidate) { recomputeAnalyses(candidate); }, routeLeavingExits, routeSharedExits) && !splitOneReturnJoin(graph, relaxReturnJoins)) {
return;
}
rebuildPredecessors(graph);