diff --git a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp index e56067458..670e4e55f 100644 --- a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp +++ b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp @@ -1454,24 +1454,19 @@ std::vector SelectionRegion(const Graph& graph, const BasicBlock& head std::vector region; std::vector pending = {header.terminator.true_block, header.terminator.false_block}; const auto* loop = FindInnermostContainingLoop(graph, header.id); - const auto global_merge = graph.FindNearestCommonPostDominator(header.terminator.true_block, - header.terminator.false_block); while (!pending.empty()) { const auto block_id = pending.back(); pending.pop_back(); if (block_id == merge || Contains(region, block_id) || - (loop != nullptr && (block_id == loop->merge || block_id == loop->continue_block)) || - IsEnclosingLinearExit(graph, header.id, block_id) || - (block_id == global_merge && HasLinearPathToTerminal(graph, block_id))) { + (loop != nullptr && (block_id == loop->merge || block_id == loop->continue_block))) { continue; } const auto* block = graph.FindBlock(block_id); if (block == nullptr) { continue; } - if (block->successors.empty()) { - continue; - } + // A return terminates its own block; a branch to a shared return still has to + // obey selection entry/exit rules, just like any other branch. AddUnique(region, block_id); pending.insert(pending.end(), block->successors.begin(), block->successors.end()); } diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 5b3d21de6..9bf0a926b 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -8328,6 +8328,38 @@ void TestNewShaderRecompilerCfgSharedReturnAfterNestedSelections() { CheckSpirvBinaryValidates(result.spirv); } +void TestNewShaderRecompilerCfgAlternatingSharedReturns() { + // PS 0x845315ec188c33db: nested conditions alternate between two shared + // terminal epilogues. Each branch must leave through its own selection merge. + const uint32_t shader[] = { + EncodeSopc(0x06, 0, 128), + EncodeSopp(0x04, 8), // outer -> second return or middle condition + EncodeSopc(0x06, 1, 128), + EncodeSopp(0x04, 2), // middle -> first return or inner condition + EncodeSopc(0x06, 2, 128), + EncodeSopp(0x04, 4), // inner -> second return or first return + EncodeVop1(0x01, 0, 129), + EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0), + EncodeSopp(0x01), + EncodeVop1(0x01, 0, 130), + EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0), + EncodeSopp(0x01), + }; + ShaderRecompiler::Decoder::Program decoded; + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); + auto graph = ShaderRecompiler::CFG::BuildGraph(decoded); + const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); + Check(graph.blocks.size() == 5u, "alternating returns fixture has the wrong CFG"); + Check(ShaderRecompiler::CFG::Structurize(graph), graph.unsupported_reason.c_str()); + Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage, + "alternating returns duplicated a terminal epilogue"); + + auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Pixel)); + Check(!result.program.dispatcher_fallback, + "alternating returns did not use structured control flow"); + CheckSpirvBinaryValidates(result.spirv); +} + void TestNewShaderRecompilerCfgLoopSharedRegion() { const uint32_t shader[] = { EncodeSopc(0x06, 0, 0), // loop condition @@ -12791,6 +12823,7 @@ int main() { TestSharedReturnPreservesDescriptorDominance(); TestNewShaderRecompilerCfgNestedTailEarlyExit(); TestNewShaderRecompilerCfgSharedReturnAfterNestedSelections(); + TestNewShaderRecompilerCfgAlternatingSharedReturns(); TestNewShaderRecompilerCfgLoopSharedRegion(); TestNewShaderRecompilerCfgSharedRegionBeforeEarlyBreakLoop(); TestNewShaderRecompilerCfgOverlappingEarlyExitLadder();