From fb94a819c7d8913c5deee0caf08d78c8fdb91cea Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 16 Sep 2026 18:58:43 +0400 Subject: [PATCH] Structure early returns with shared loop continuations --- .../recompiler/frontend/cfg/ShaderCFG.cpp | 9 ++-- tests/shaderCfgTests.cpp | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp index bb9058375..216e57202 100644 --- a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp +++ b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp @@ -1206,13 +1206,12 @@ uint32_t FindSelectionMerge(const Graph& graph, const BasicBlock& block) { return false_target; } // A return can leave a selection without reaching its merge. Keep the - // continuing arm as the merge instead of joining live state with a return. - if (graph.Dominates(block.id, false_target) && - HasLinearPathToTerminal(graph, true_target)) { + // continuing arm as the merge. Shared continuations receive a dedicated + // gateway in SplitSharedMergeBlock before structured control is emitted. + if (HasLinearPathToTerminal(graph, true_target)) { return false_target; } - if (graph.Dominates(block.id, true_target) && - HasLinearPathToTerminal(graph, false_target)) { + if (HasLinearPathToTerminal(graph, false_target)) { return true_target; } } diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 1ac6d1231..ad3a2aed0 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -8762,6 +8762,54 @@ void TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal() { CheckSpirvBinaryValidates(result.spirv); } +void TestNewShaderRecompilerCfgEarlyReturnSharedLoopContinuation() { + // PS d6fb5f22c689ceff: an outer EXEC skip and an inner SCC exit share the + // continuing arm, which contains another loop and reaches a different return. + const uint32_t shader[] = { + EncodeSopc(0x06, 0, 128), + EncodeSopp(0x04, 7), // outer -> shared loop or preceding work + EncodeSMovB32(2, 129), + EncodeSopc(0x06, 2, 128), // preceding work loop + EncodeSopp(0x04, 2), // loop -> early-return condition or body + EncodeSop2(0x01, 2, 2, 129), + EncodeSopp(0x02, -4), + EncodeSopc(0x06, 1, 128), + EncodeSopp(0x04, 5), // inner -> private return or shared loop + EncodeSopc(0x06, 3, 128), // shared loop header + EncodeSopp(0x04, 2), // loop -> normal return or body + EncodeSop2(0x01, 3, 3, 129), + EncodeSopp(0x02, -4), // body -> loop header + 0xbf810000u, // normal return + EncodeSMovB32(4, 130), // private return epilogue + 0xbf810000u, + }; + + ShaderRecompiler::Decoder::Program decoded; + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); + auto graph = ShaderRecompiler::CFG::BuildGraph(decoded); + const auto original_coverage = + CfgInstructionCoverage(graph, decoded.instructions.size()); + Check(ShaderRecompiler::CFG::Structurize(graph), + graph.unsupported_reason.c_str()); + Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == + original_coverage && + graph.natural_loops.size() == 2u, + "shared-continuation gateway changed semantic blocks or the loops"); + Check(std::ranges::none_of(graph.blocks, + [](const auto &block) { + return block.terminator.goto_variable != + UINT32_MAX; + }), + "private early return introduced unnecessary routing state"); + + auto options = MakeCompileOptions(ShaderType::Compute); + auto result = RecompileForTest(shader, options); + Check(!result.program.dispatcher_fallback && + SpirvInstructionOpcodeCount(result.spirv, 251) == 0u, + "early return with a shared loop continuation selected the dispatcher"); + CheckSpirvBinaryValidates(result.spirv); +} + void TestNewShaderRecompilerCfgSharedTerminalEarlyExit() { const uint32_t shader[] = { EncodeSopc(0x06, 0, 0), // outer early-exit condition @@ -13491,6 +13539,7 @@ int main() { TestNewShaderRecompilerCfgSharedRegionBeforeEarlyBreakLoop(); TestNewShaderRecompilerCfgOverlappingEarlyExitLadder(); TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal(); + TestNewShaderRecompilerCfgEarlyReturnSharedLoopContinuation(); TestNewShaderRecompilerCfgSharedTerminalEarlyExit(); TestNewShaderRecompilerCfgPrunesUnreachableSelectionEntry(); TestNewShaderRecompilerCfgFailedStructurizationPreservesGraph();