mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
shader cfg: structurize overlapping early-exit ladders
Trace the dispatcher fallback for PS 0x9e1133a6 to an acyclic RDNA2 S_CBRANCH_SCC0 ladder. Several inner headers reach both a shared continuation and a shared terminal, so they do not dominate the common post-dominator. The old shared-merge splitter redirected only the dominated terminal edge; the merge therefore remained shared and forced dispatcher lowering. Canonicalize this control flow with bounded cross-entry tail duplication: - find the complete selection region before its post-dominator - clone only region blocks that the header does not dominate - redirect header-owned edges into those clones - join every owned exit through a private synthetic merge - retain the existing dispatcher when loops, invalid merges, or growth bounds make duplication inappropriate Add the recovered seven-block topology as a focused regression. Assert SCC0 taken/fallthrough orientation, unique merges, full post-transform reachability, structured lowering without OpSwitch, and valid SPIR-V. Validation: - shader_cfg_tests --overlapping-cfg-only - shader_cfg_tests --loop-break-merge-only - shader_cfg_tests --loop-canonicalization-only - kyty_emulator built with _Build/vscode-clang - launch.json visible run advanced continuously to frame 570 - observed seven-block PS e83e3fb5 structured into 19 blocks without fallback - run stopped at the separately deferred sparse PRT BufferCache backing fatal Independent audits verified progress, ID remapping, vector lifetime, invalid merge handling, bounded termination, and dirty-tree staging scope.
This commit is contained in:
@@ -1098,7 +1098,102 @@ bool SplitOneLoopMerge(Graph& graph) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SplitOneSelectionMerge(Graph& graph) {
|
||||
std::vector<uint32_t> SelectionRegion(const Graph& graph, const BasicBlock& header,
|
||||
uint32_t merge) {
|
||||
std::vector<uint32_t> region;
|
||||
std::vector<uint32_t> pending = {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)) {
|
||||
continue;
|
||||
}
|
||||
const auto* block = graph.FindBlock(block_id);
|
||||
if (block == nullptr) {
|
||||
continue;
|
||||
}
|
||||
AddUnique(region, block_id);
|
||||
pending.insert(pending.end(), block->successors.begin(), block->successors.end());
|
||||
}
|
||||
SortUnique(region);
|
||||
return region;
|
||||
}
|
||||
|
||||
bool DuplicateSelectionRegion(Graph& graph, uint32_t header_id, uint32_t merge,
|
||||
const std::vector<uint32_t>& region, uint32_t block_budget) {
|
||||
std::vector<uint32_t> cloned_blocks;
|
||||
for (auto block_id: region) {
|
||||
if (!graph.Dominates(header_id, block_id)) {
|
||||
cloned_blocks.push_back(block_id);
|
||||
}
|
||||
}
|
||||
if (cloned_blocks.empty() || graph.FindBlock(header_id) == nullptr || header_id >= merge ||
|
||||
graph.blocks.size() + cloned_blocks.size() + 1u > block_budget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto first_clone = static_cast<uint32_t>(graph.blocks.size());
|
||||
std::map<uint32_t, uint32_t> clones;
|
||||
for (uint32_t i = 0; i < cloned_blocks.size(); i++) {
|
||||
clones.emplace(cloned_blocks[i], first_clone + i);
|
||||
}
|
||||
|
||||
for (auto block_id: cloned_blocks) {
|
||||
BasicBlock clone = *graph.FindBlock(block_id);
|
||||
clone.id = clones.at(block_id);
|
||||
clone.predecessors.clear();
|
||||
clone.dominators.clear();
|
||||
clone.post_dominators.clear();
|
||||
graph.blocks.push_back(std::move(clone));
|
||||
}
|
||||
|
||||
const auto remap_block = [&](BasicBlock& block) {
|
||||
const auto remap_target = [&](uint32_t& target) {
|
||||
if (const auto it = clones.find(target); it != clones.end()) {
|
||||
target = it->second;
|
||||
}
|
||||
};
|
||||
for (auto& successor: block.successors) {
|
||||
remap_target(successor);
|
||||
}
|
||||
remap_target(block.terminator.true_block);
|
||||
remap_target(block.terminator.false_block);
|
||||
remap_target(block.terminator.merge_block);
|
||||
remap_target(block.terminator.continue_block);
|
||||
for (auto& target: block.terminator.indirect_targets) {
|
||||
remap_target(target);
|
||||
}
|
||||
};
|
||||
for (auto block_id: region) {
|
||||
const auto owned_id = clones.contains(block_id) ? clones.at(block_id) : block_id;
|
||||
remap_block(*graph.FindBlock(owned_id));
|
||||
}
|
||||
|
||||
const auto private_merge = AppendSyntheticBranchBlock(graph, merge);
|
||||
auto& header = *graph.FindBlock(header_id);
|
||||
remap_block(header);
|
||||
|
||||
for (auto block_id: region) {
|
||||
const auto owned_id = clones.contains(block_id) ? clones.at(block_id) : block_id;
|
||||
auto* block = graph.FindBlock(owned_id);
|
||||
if (block != nullptr) {
|
||||
ReplaceValue(block->successors, merge, private_merge);
|
||||
ReplaceTerminatorTarget(block->terminator, merge, private_merge);
|
||||
}
|
||||
}
|
||||
ReplaceValue(header.successors, merge, private_merge);
|
||||
ReplaceTerminatorTarget(header.terminator, merge, private_merge);
|
||||
|
||||
for (uint32_t i = 0; i <= cloned_blocks.size(); i++) {
|
||||
MoveBlockBefore(graph, first_clone + i, merge + i);
|
||||
}
|
||||
RebuildPredecessors(graph);
|
||||
RecomputeAnalyses(graph);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SplitOneSelectionMerge(Graph& graph, uint32_t block_budget) {
|
||||
std::vector<uint32_t> loop_headers;
|
||||
loop_headers.reserve(graph.natural_loops.size());
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
@@ -1118,6 +1213,18 @@ bool SplitOneSelectionMerge(Graph& graph) {
|
||||
|
||||
const auto merge = graph.FindNearestCommonPostDominator(block->terminator.true_block,
|
||||
block->terminator.false_block);
|
||||
if (merge == UINT32_MAX || graph.FindBlock(merge) == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const auto region = SelectionRegion(graph, *block, merge);
|
||||
if (std::any_of(region.begin(), region.end(),
|
||||
[&](uint32_t member) { return !graph.Dominates(block_id, member); })) {
|
||||
if (graph.natural_loops.empty() &&
|
||||
DuplicateSelectionRegion(graph, block_id, merge, region, block_budget)) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto construct_blocks = DominatedBlocks(graph, block_id, merge);
|
||||
const auto force_split = MergeLeavesContainingLoop(graph, block_id, merge);
|
||||
if (SplitSharedMergeBlock(graph, merge, construct_blocks, force_split)) {
|
||||
@@ -1129,10 +1236,12 @@ bool SplitOneSelectionMerge(Graph& graph) {
|
||||
|
||||
bool SplitSharedMergeBlocks(Graph& graph, std::string* error) {
|
||||
const auto original_block_count = static_cast<uint32_t>(graph.blocks.size());
|
||||
const auto split_budget =
|
||||
std::max<uint32_t>(16u, std::min<uint32_t>(128u, original_block_count));
|
||||
const auto split_budget = std::max<uint32_t>(
|
||||
16u, std::min<uint32_t>(128u, original_block_count * 4u));
|
||||
const auto block_budget = std::max<uint32_t>(
|
||||
32u, std::min<uint32_t>(512u, original_block_count * 8u));
|
||||
for (uint32_t splits = 0; splits < split_budget; splits++) {
|
||||
if (!SplitOneLoopMerge(graph) && !SplitOneSelectionMerge(graph)) {
|
||||
if (!SplitOneLoopMerge(graph) && !SplitOneSelectionMerge(graph, block_budget)) {
|
||||
return true;
|
||||
}
|
||||
RebuildPredecessors(graph);
|
||||
|
||||
@@ -5770,6 +5770,85 @@ void TestNewShaderRecompilerCfgDuplicateMergeStructuredSplit() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgOverlappingEarlyExitLadder() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x06, 0, 0), // block 0
|
||||
EncodeSopp(0x04, 2), // block 0 -> 2 or 1
|
||||
EncodeSopc(0x06, 1, 1), // block 1
|
||||
EncodeSopp(0x04, 6), // block 1 -> 5 or 2
|
||||
EncodeSopc(0x06, 2, 2), // block 2
|
||||
EncodeSopp(0x04, 4), // block 2 -> 5 or 3
|
||||
EncodeSopc(0x06, 3, 3), // block 3
|
||||
EncodeSopp(0x04, 2), // block 3 -> 5 or 4
|
||||
EncodeSMovB32(4, 129), // block 4
|
||||
0xbf810000u, // block 4 -> 6
|
||||
EncodeSMovB32(5, 129), // block 5
|
||||
0xbf810000u, // block 5 -> 6
|
||||
};
|
||||
|
||||
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());
|
||||
Check(graph.blocks.size() == 7u && graph.blocks[0].successors == std::vector<uint32_t>({1, 2}) &&
|
||||
graph.blocks[0].terminator.true_block == 2u &&
|
||||
graph.blocks[0].terminator.false_block == 1u &&
|
||||
graph.blocks[1].successors == std::vector<uint32_t>({2, 5}) &&
|
||||
graph.blocks[1].terminator.true_block == 5u &&
|
||||
graph.blocks[1].terminator.false_block == 2u &&
|
||||
graph.blocks[2].successors == std::vector<uint32_t>({3, 5}) &&
|
||||
graph.blocks[2].terminator.true_block == 5u &&
|
||||
graph.blocks[2].terminator.false_block == 3u &&
|
||||
graph.blocks[3].successors == std::vector<uint32_t>({4, 5}) &&
|
||||
graph.blocks[3].terminator.true_block == 5u &&
|
||||
graph.blocks[3].terminator.false_block == 4u &&
|
||||
graph.blocks[4].successors == std::vector<uint32_t>({6}) &&
|
||||
graph.blocks[5].successors == std::vector<uint32_t>({6}),
|
||||
"overlapping early-exit fixture does not match the observed shader CFG");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str());
|
||||
std::vector<bool> reachable(graph.blocks.size());
|
||||
std::vector<uint32_t> pending = {graph.entry_block};
|
||||
while (!pending.empty()) {
|
||||
const auto block_id = pending.back();
|
||||
pending.pop_back();
|
||||
if (reachable[block_id]) {
|
||||
continue;
|
||||
}
|
||||
reachable[block_id] = true;
|
||||
pending.insert(pending.end(), graph.blocks[block_id].successors.begin(),
|
||||
graph.blocks[block_id].successors.end());
|
||||
}
|
||||
Check(std::all_of(reachable.begin(), reachable.end(), [](bool value) { return value; }),
|
||||
"overlapping early-exit structurization left unreachable blocks");
|
||||
std::vector<uint32_t> merges;
|
||||
for (const auto& block: graph.blocks) {
|
||||
if (block.terminator.kind == ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch) {
|
||||
Check(block.terminator.merge_block != UINT32_MAX &&
|
||||
std::find(merges.begin(), merges.end(), block.terminator.merge_block) ==
|
||||
merges.end(),
|
||||
"overlapping early-exit structurization retained a shared merge");
|
||||
merges.push_back(block.terminator.merge_block);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Pixel;
|
||||
options.dump_ir = true;
|
||||
ShaderRecompiler::CompileResult result;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
|
||||
Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
|
||||
"overlapping early-exit ladder did not stay on the structured path");
|
||||
Check(!Common::ContainsStr(result.ir_dump, "duplicate structured merge block"),
|
||||
"overlapping early-exit ladder retained a shared merge");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 247) >= 4u,
|
||||
"overlapping early-exit ladder lost its selections");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
||||
"overlapping early-exit ladder used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgIrreducibleDispatcher() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopp(0x05, 2), // entry -> B, fallthrough A
|
||||
@@ -7409,6 +7488,7 @@ int main() {
|
||||
TestNewShaderRecompilerCfgConditionalLoopHeaderSelection();
|
||||
TestNewShaderRecompilerCfgMultipleLoopLatches();
|
||||
TestNewShaderRecompilerCfgDuplicateMergeStructuredSplit();
|
||||
TestNewShaderRecompilerCfgOverlappingEarlyExitLadder();
|
||||
TestNewShaderRecompilerCfgIrreducibleDispatcher();
|
||||
TestNewShaderRecompilerExecMaskHelpers();
|
||||
TestComputeShaderInputWaveSize();
|
||||
|
||||
Reference in New Issue
Block a user