From a2f497a05c5c83faee3bbdba84920fefbfaf8e5e Mon Sep 17 00:00:00 2001 From: fdfawef324 <287205748+fdfawef324@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:33:45 -0400 Subject: [PATCH] fix(shader): give a selection a merge when one arm returns through nested branches mergeBesideReturns ignores the edges into blocks that only lead to a return when it looks for the merge of a selection whose arms do not meet. It only recognized a straight run of single-predecessor blocks ending in a return, so an arm whose return path branches again (an early return inside a nested if/else) still counted as a block of its own, the arms had no common post-dominator, and the whole program fell back to flag structurization ("conditional block 0 has no structured merge"). A block now returns when it has one predecessor, is dominated by the header and every successor returns. Flag structurization turns every original block into a guarded region, and the register phis at the region joins carry values from paths that never reach a later use: in Resident Evil 4 (PPSA07411) pixel program 0x20762add00 the T# of an image_load became Phi(0, load through Phi(user data, compare mask)) and resource analysis refused it. Structured, the T# is the load through the user-data pointer and the program prepares, captures and materializes. Co-Authored-By: Claude Opus 5.5 --- .../libSceAgcDriver/tests/Structurizer.cpp | 15 +++++++++++ .../ControlFlow/src/Structurizer.cpp | 25 +++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp b/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp index ca8b02d465..c3f0359632 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp @@ -1,6 +1,7 @@ #include "ControlFlow/Structurizer.hpp" #include #include +#include #include #include #include @@ -251,6 +252,17 @@ void requireSameStructuredExecutions(const std::vector>& successors, const char* name) { + if (std::getenv("APS5_FORCE_STRUCTURIZER_FALLBACK") != nullptr) return; + auto graph = makeGraph(successors); + Structurizer{}.Structurize(graph); + for (const auto& block : graph.blocks) { + if (block.terminator.condition == BranchCondition::GotoVariable || block.terminator.gotoValue >= 0) { + throw std::runtime_error(std::string(name) + ": block " + std::to_string(block.id) + " routes through a goto variable instead of a structured selection"); + } + } +} + void requireGrowthBudgetRefusal(ControlFlowGraph graph, const char* name) { const std::string growthBudget = "CFG shared merge splitting exceeded growth budget"; try { @@ -423,6 +435,9 @@ int main() { 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"); + const std::vector> branchingReturn{{5, 1}, {3, 2}, {13}, {9, 4}, {13}, {10, 6}, {8, 7}, {13}, {13, 9}, {13}, {12, 11}, {12}, {13}, {}}; + requireSameStructuredExecutions(branchingReturn, "an arm whose returns branch once a tail shared across arms is cloned"); + requireNoRouteVariables(branchingReturn, "an arm whose returns branch once a tail shared across arms is cloned"); std::vector> diamonds; for (std::uint32_t diamond = 0; diamond < 50u; ++diamond) { const auto head = diamond * 3u; diff --git a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp index c78d429445..0d14586500 100644 --- a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp +++ b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp @@ -441,12 +441,27 @@ std::uint32_t mergeBesideReturns(const ControlFlowGraph& graph, const BasicBlock } } const auto count = static_cast(graph.blocks.size()); - const auto returns = [&](std::uint32_t blockId) { - if (!graph.Dominates(header.id, blockId) || !hasLinearPathToTerminal(graph, blockId)) { - return false; + enum class Returning : std::uint8_t { Unknown, Visiting, No, Yes }; + std::vector returning(count, Returning::Unknown); + const auto returns = [&](std::uint32_t start) { + std::vector pending{start}; + while (!pending.empty()) { + const auto blockId = pending.back(); + const auto& block = graph.FindBlock(blockId); + if (returning[blockId] == Returning::Unknown) { + if (block.predecessors.size() == 1u && graph.Dominates(header.id, blockId)) { + returning[blockId] = Returning::Visiting; + std::copy_if(block.successors.begin(), block.successors.end(), std::back_inserter(pending), [&](std::uint32_t successor) { return returning[successor] == Returning::Unknown; }); + continue; + } + returning[blockId] = Returning::No; + } else if (returning[blockId] == Returning::Visiting) { + const bool privateReturn = std::all_of(block.successors.begin(), block.successors.end(), [&](std::uint32_t successor) { return returning[successor] == Returning::Yes; }); + returning[blockId] = privateReturn ? Returning::Yes : Returning::No; + } + pending.pop_back(); } - const auto tail = linearTail(graph, blockId); - return std::all_of(tail.begin(), tail.end(), [&](std::uint32_t member) { return graph.FindBlock(member).predecessors.size() == 1u; }); + return returning[start] == Returning::Yes; }; std::vector> kept(count); std::vector> postDominators(count, allBlockIds(count));