shader: fix shared-terminal selection merges

This commit is contained in:
nmzik
2026-08-19 16:55:37 +02:00
parent 41bfba515c
commit 2db0b3cfa7
2 changed files with 50 additions and 22 deletions
@@ -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<bool> 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<bool> 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(
+45
View File
@@ -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();