diff --git a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp index 5ebc73d7f..7bb67e677 100644 --- a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp +++ b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp @@ -1138,20 +1138,6 @@ bool IsLoopControlGateway(const Graph& graph, const NaturalLoop& loop, uint32_t is_control_target(block->terminator.false_block); } -bool HasLinearPathTo(const Graph& graph, uint32_t start, uint32_t target) { - std::vector visited(graph.blocks.size(), false); - for (auto block_id = start; block_id != target;) { - const auto* block = graph.FindBlock(block_id); - if (block == nullptr || block_id >= visited.size() || visited[block_id] || - block->successors.size() != 1) { - return false; - } - visited[block_id] = true; - block_id = block->successors.front(); - } - return true; -} - bool HasLinearPathToTerminal(const Graph& graph, uint32_t start) { std::vector visited(graph.blocks.size(), false); for (auto block_id = start;;) { @@ -1213,13 +1199,6 @@ uint32_t FindSelectionMerge(const Graph& graph, const BasicBlock& block) { const auto true_target = block.terminator.true_block; const auto false_target = block.terminator.false_block; if (global_block != nullptr && global_block->successors.empty()) { - // A terminal common post-dominator can hide the local continuation of a nested - // early-exit selection. Prefer that continuation as the structured merge. - const bool true_exits = HasLinearPathTo(graph, true_target, global_merge); - const bool false_exits = HasLinearPathTo(graph, false_target, global_merge); - if (true_exits != false_exits) { - return true_exits ? false_target : true_target; - } const bool false_reaches_true = CanReachBefore(graph, false_target, true_target, global_merge); const bool true_reaches_false = @@ -1511,7 +1490,11 @@ bool SplitOneSelectionMerge(Graph& graph, std::string* error) { } const auto region = SelectionRegion(graph, *block, merge); const auto external = std::find_if(region.begin(), region.end(), [&](uint32_t member) { - return !graph.Dominates(block_id, member); + const auto* member_block = graph.FindBlock(member); + return member_block != nullptr && + std::ranges::any_of(member_block->predecessors, [&](uint32_t predecessor) { + return predecessor != block_id && !Contains(region, predecessor); + }); }); if (external != region.end()) { SetFailure( diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index c980b9de5..fcc21dac2 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -7881,6 +7881,50 @@ void TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal() { CheckSpirvBinaryValidates(result.spirv); } +void TestNewShaderRecompilerCfgSharedTerminalEarlyExit() { + const uint32_t shader[] = { + EncodeSopc(0x06, 0, 0), // outer early-exit condition + EncodeSopp(0x04, 8), // outer -> early exit or nested body + EncodeSopc(0x06, 1, 1), // nested body condition + EncodeSopp(0x04, 1), // nested body -> right or left + EncodeSopp(0x02, 4), // left -> body continuation + EncodeSopc(0x06, 2, 2), // right condition + EncodeSopp(0x04, 1), // right -> join or work + EncodeSopp(0x02, 0), // work -> join + EncodeSopp(0x02, 0), // join -> body continuation + EncodeSopp(0x02, 1), // body continuation -> shared terminal + EncodeSopp(0x02, 0), // early exit -> shared terminal + 0xbf810000u, + }; + + ShaderRecompiler::Decoder::Program decoded; + std::string error; + Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, + &error), + error.c_str()); + ShaderRecompiler::CFG::Graph graph; + Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), + error.c_str()); + const auto original_coverage = + CfgInstructionCoverage(graph, decoded.instructions.size()); + Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str()); + Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == + original_coverage, + "shared-terminal structurization changed semantic instruction coverage"); + + auto options = MakeCompileOptions(ShaderType::Pixel); + options.dump_ir = true; + ShaderRecompiler::CompileResult result; + Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), + error.c_str()); + Check(!result.program.dispatcher_fallback && + Common::ContainsStr(result.ir_dump, "mode=structured"), + "shared-terminal early exit selected dispatcher control flow"); + Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u, + "shared-terminal early exit unexpectedly used dispatcher OpSwitch"); + CheckSpirvBinaryValidates(result.spirv); +} + void TestNewShaderRecompilerCfgPrunesUnreachableSelectionEntry() { const uint32_t shader[] = { EncodeSopp(0x02, 1), // entry -> header, skipping unreachable entry @@ -11513,6 +11557,7 @@ int main() { TestNewShaderRecompilerCfgLoopSharedRegion(); TestNewShaderRecompilerCfgOverlappingEarlyExitLadder(); TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal(); + TestNewShaderRecompilerCfgSharedTerminalEarlyExit(); TestNewShaderRecompilerCfgPrunesUnreachableSelectionEntry(); TestNewShaderRecompilerCfgIrreducibleDispatcher(); TestNewShaderRecompilerDispatcherSpillsU32x3();