diff --git a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp index eed9df358..fe95b4849 100644 --- a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp +++ b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp @@ -1051,15 +1051,6 @@ bool IsolateSemanticLoopHeader(Graph& graph, uint32_t old_header) { return true; } -bool IsSyntheticMergeForwarder(const Graph& graph, uint32_t block_id, uint32_t merge) { - const auto* block = graph.FindBlock(block_id); - return block != nullptr && block->inst_begin == block->inst_end && - block->successors.size() == 1u && block->successors.front() == merge && - block->terminator.kind == TerminatorKind::Branch && - block->terminator.condition == BranchCondition::Always && - block->terminator.true_block == merge; -} - const NaturalLoop* FindInnermostContainingLoop(const Graph& graph, uint32_t block_id) { const NaturalLoop* innermost = nullptr; for (const auto& loop: graph.natural_loops) { @@ -1232,24 +1223,14 @@ bool SplitSharedMergeBlock(Graph& graph, uint32_t merge, if (construct_predecessors.empty() || (!force_split && external_predecessors.empty())) { return false; } - std::vector predecessors_to_split; - for (auto pred: construct_predecessors) { - if (!IsSyntheticMergeForwarder(graph, pred, merge)) { - AddUnique(predecessors_to_split, pred); - } - } - if (predecessors_to_split.empty()) { - return false; - } - const auto synthetic_merge = AppendSyntheticBranchBlock(graph, merge); auto* synthetic_block = graph.FindBlock(synthetic_merge); if (synthetic_block != nullptr) { - synthetic_block->predecessors = predecessors_to_split; + synthetic_block->predecessors = construct_predecessors; SortUnique(synthetic_block->predecessors); } - for (auto pred: predecessors_to_split) { + for (auto pred: construct_predecessors) { auto* block = graph.FindBlock(pred); if (block == nullptr) { continue; @@ -1260,7 +1241,7 @@ bool SplitSharedMergeBlock(Graph& graph, uint32_t merge, auto* old_merge = graph.FindBlock(merge); if (old_merge != nullptr) { - for (auto pred: predecessors_to_split) { + for (auto pred: construct_predecessors) { RemoveValue(old_merge->predecessors, pred); } AddUnique(old_merge->predecessors, synthetic_merge); @@ -1313,11 +1294,24 @@ bool SplitOneSelectionMerge(Graph& graph, std::string* error) { AddUnique(loop_headers, loop.header); } - const auto original_block_count = static_cast(graph.blocks.size()); - for (uint32_t block_id = 0; block_id < original_block_count; block_id++) { + std::vector selection_headers; + for (const auto& block: graph.blocks) { + if (block.terminator.kind == TerminatorKind::ConditionalBranch && + !Contains(loop_headers, block.id)) { + selection_headers.push_back(block.id); + } + } + std::sort(selection_headers.begin(), selection_headers.end(), [&](uint32_t lhs, uint32_t rhs) { + const auto* lhs_block = graph.FindBlock(lhs); + const auto* rhs_block = graph.FindBlock(rhs); + const auto lhs_depth = lhs_block != nullptr ? lhs_block->dominators.size() : 0u; + const auto rhs_depth = rhs_block != nullptr ? rhs_block->dominators.size() : 0u; + return lhs_depth != rhs_depth ? lhs_depth > rhs_depth : lhs < rhs; + }); + + for (const auto block_id: selection_headers) { const auto* block = graph.FindBlock(block_id); - if (block == nullptr || block->terminator.kind != TerminatorKind::ConditionalBranch || - Contains(loop_headers, block_id)) { + if (block == nullptr) { continue; } if (IsInnermostLoopControlConditional(graph, *block)) { @@ -1352,8 +1346,7 @@ bool SplitOneSelectionMerge(Graph& graph, std::string* error) { bool SplitSharedMergeBlocks(Graph& graph, std::string* error) { const auto original_block_count = static_cast(graph.blocks.size()); - const auto split_budget = - std::max(16u, std::min(128u, original_block_count * 4u)); + const auto split_budget = std::max(16u, original_block_count * 4u); for (uint32_t splits = 0; splits < split_budget; splits++) { if (!SplitOneLoopMerge(graph) && !SplitOneSelectionMerge(graph, error)) { return !graph.unsupported; diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 5d9c28a7f..370584211 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -6943,6 +6943,80 @@ void TestNewShaderRecompilerCfgDuplicateMergeStructuredSplit() { CheckSpirvBinaryValidates(result.spirv); } +void TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders() { + const uint32_t shader[] = { + EncodeSopc(0x06, 0, 0), // preheader condition + EncodeSopp(0x05, 10), // preheader early exit -> end + EncodeSopc(0x06, 1, 1), // outer condition + EncodeSopp(0x05, 8), // outer early exit -> end + EncodeSopc(0x06, 2, 2), // inner condition + EncodeSopp(0x05, 2), // inner -> loop, else linear arm + EncodeSMovB32(3, 129), // linear arm work + EncodeSopp(0x02, 4), // linear arm -> end + EncodeSopc(0x06, 4, 4), // loop header condition + EncodeSopp(0x04, 2), // loop exit -> end + EncodeSop2(0x00, 5, 5, 129), // loop work + EncodeSopp(0x02, 0xfffcu), // backedge -> loop header + 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_block_count = graph.blocks.size(); + const auto original_coverage = + CfgInstructionCoverage(graph, decoded.instructions.size()); + const auto original_empty_blocks = + std::ranges::count_if(graph.blocks, [](const auto &block) { + return block.inst_begin == block.inst_end; + }); + Check(original_block_count == 8u && graph.natural_loops.size() == 1u, + "nested early-exit fixture has the wrong native CFG"); + const bool structured = ShaderRecompiler::CFG::Structurize(graph, &error); + Check(structured, error.c_str()); + Check(graph.blocks.size() == original_block_count + 3u && + CfgInstructionCoverage(graph, decoded.instructions.size()) == + original_coverage && + std::ranges::count_if(graph.blocks, + [](const auto &block) { + return block.inst_begin == block.inst_end; + }) == original_empty_blocks + 3, + "nested early-exit structurization changed semantic coverage"); + const auto *preheader = graph.FindBlockByPc(0x00u); + const auto *outer = graph.FindBlockByPc(0x08u); + const auto *inner = graph.FindBlockByPc(0x10u); + const auto *loop = graph.FindBlockByPc(0x20u); + Check( + preheader != nullptr && outer != nullptr && inner != nullptr && + loop != nullptr && loop->terminator.loop_header && + preheader->terminator.merge_block != UINT32_MAX && + outer->terminator.merge_block != UINT32_MAX && + inner->terminator.merge_block != UINT32_MAX && + preheader->terminator.merge_block != outer->terminator.merge_block && + preheader->terminator.merge_block != inner->terminator.merge_block && + outer->terminator.merge_block != inner->terminator.merge_block, + "nested early-exit constructs do not have distinct structured merges"); + + auto options = MakeCompileOptions(ShaderType::Compute); + 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"), + "nested early-exit loop unexpectedly selected dispatcher fallback"); + Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 3u && + SpirvInstructionOpcodeCount(result.spirv, 246) == 1u && + SpirvInstructionOpcodeCount(result.spirv, 251) == 0u, + "nested early-exit SPIR-V has the wrong structured control flow"); + CheckSpirvBinaryValidates(result.spirv); +} + void TestNewShaderRecompilerCfgExecSccSharedArm() { const uint32_t shader[] = { EncodeSop2(0x15, 126, 4, 126), // s_andn2_b64 exec, s4, exec @@ -9333,6 +9407,7 @@ int main() { TestNewShaderRecompilerCfgConditionalLoopHeaderSelection(); TestNewShaderRecompilerCfgMultipleLoopLatches(); TestNewShaderRecompilerCfgDuplicateMergeStructuredSplit(); + TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders(); TestNewShaderRecompilerCfgExecSccSharedArm(); TestNewShaderRecompilerCfgOverlappingEarlyExitLadder(); TestNewShaderRecompilerCfgExternallyEnteredSelectionDispatcher();