mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-25 20:36:54 +01:00
shader: keep loop branches during selection routing
This commit is contained in:
@@ -1718,7 +1718,8 @@ void AppendEnclosingRouteBlocks(Graph& graph, uint32_t original_block_count,
|
|||||||
for (uint32_t candidate = 0; candidate < original_block_count; candidate++) {
|
for (uint32_t candidate = 0; candidate < original_block_count; candidate++) {
|
||||||
const auto* candidate_block = graph.FindBlock(candidate);
|
const auto* candidate_block = graph.FindBlock(candidate);
|
||||||
if (candidate_block == nullptr ||
|
if (candidate_block == nullptr ||
|
||||||
candidate_block->terminator.kind != TerminatorKind::ConditionalBranch) {
|
candidate_block->terminator.kind != TerminatorKind::ConditionalBranch ||
|
||||||
|
IsInnermostLoopControlConditional(graph, *candidate_block)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto candidate_true = candidate_block->terminator.true_block;
|
const auto candidate_true = candidate_block->terminator.true_block;
|
||||||
@@ -1752,11 +1753,15 @@ void AppendEnclosingRouteBlocks(Graph& graph, uint32_t original_block_count,
|
|||||||
|
|
||||||
// Turn H0 -> shared/H1, H1 -> shared/other into nested selections whose empty
|
// Turn H0 -> shared/H1, H1 -> shared/other into nested selections whose empty
|
||||||
// forwarding blocks set one typed SSA route value. Guest semantic blocks remain unique.
|
// forwarding blocks set one typed SSA route value. Guest semantic blocks remain unique.
|
||||||
|
// Preserve loop-control branches just as SplitOneSelectionMerge does. Moving a loop
|
||||||
|
// exit through the route selector can create an inner cycle whose merge is also the
|
||||||
|
// enclosing loop merge, so splitting that shared exit can never separate the loops.
|
||||||
bool RouteOneSharedArm(Graph& graph, uint32_t original_block_count, uint32_t outer_id,
|
bool RouteOneSharedArm(Graph& graph, uint32_t original_block_count, uint32_t outer_id,
|
||||||
uint32_t route_variable, GotoRouteBlocks& route) {
|
uint32_t route_variable, GotoRouteBlocks& route) {
|
||||||
auto* outer = graph.FindBlock(outer_id);
|
auto* outer = graph.FindBlock(outer_id);
|
||||||
if (outer == nullptr || outer->inst_begin == outer->inst_end ||
|
if (outer == nullptr || outer->inst_begin == outer->inst_end ||
|
||||||
outer->terminator.kind != TerminatorKind::ConditionalBranch) {
|
outer->terminator.kind != TerminatorKind::ConditionalBranch ||
|
||||||
|
IsInnermostLoopControlConditional(graph, *outer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1768,7 +1773,8 @@ bool RouteOneSharedArm(Graph& graph, uint32_t original_block_count, uint32_t out
|
|||||||
const auto* inner = graph.FindBlock(inner_id);
|
const auto* inner = graph.FindBlock(inner_id);
|
||||||
if (inner == nullptr || inner->inst_begin == inner->inst_end ||
|
if (inner == nullptr || inner->inst_begin == inner->inst_end ||
|
||||||
inner->predecessors != std::vector<uint32_t> {outer_id} ||
|
inner->predecessors != std::vector<uint32_t> {outer_id} ||
|
||||||
inner->terminator.kind != TerminatorKind::ConditionalBranch) {
|
inner->terminator.kind != TerminatorKind::ConditionalBranch ||
|
||||||
|
IsInnermostLoopControlConditional(graph, *inner)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8375,6 +8375,60 @@ void TestNewShaderRecompilerCfgLoopSharedRegion() {
|
|||||||
CheckSpirvBinaryValidates(result.spirv);
|
CheckSpirvBinaryValidates(result.spirv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestNewShaderRecompilerCfgSharedRegionBeforeEarlyBreakLoop() {
|
||||||
|
// The first selection needs routing at its externally entered common tail.
|
||||||
|
// A later loop has the same lexical shared-arm shape, but its exit branches
|
||||||
|
// are already structured and must retain their loop-control role.
|
||||||
|
const uint32_t shader[] = {
|
||||||
|
EncodeSopc(0x06, 0, 0), // 0: enclosing selection
|
||||||
|
EncodeSopp(0x04, 4), // 0 -> 3 or 1
|
||||||
|
EncodeSopc(0x06, 1, 1), // 1: inner selection
|
||||||
|
EncodeSopp(0x04, 6), // 1 -> 5 or 2
|
||||||
|
EncodeSMovB32(2, 129), // 2: inner body
|
||||||
|
EncodeSopp(0x02, 2), // 2 -> 4
|
||||||
|
EncodeSMovB32(3, 129), // 3: outer arm
|
||||||
|
EncodeSopp(0x02, 0), // 3 -> 4
|
||||||
|
EncodeSMovB32(4, 129), // 4: shared tail
|
||||||
|
EncodeSopp(0x02, 0), // 4 -> 5
|
||||||
|
EncodeSopc(0x06, 5, 5), // 5: loop header
|
||||||
|
EncodeSopp(0x04, 4), // 5 -> 8 or 6
|
||||||
|
EncodeSopc(0x06, 6, 6), // 6: early break
|
||||||
|
EncodeSopp(0x04, 2), // 6 -> 8 or 7
|
||||||
|
EncodeSopc(0x06, 7, 7), // 7: loop latch
|
||||||
|
EncodeSopp(0x05, 0xfffau), // 7 -> 5 or 8
|
||||||
|
EncodeSMovB32(8, 129), // 8: terminal
|
||||||
|
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(graph.blocks.size() == 9u && graph.natural_loops.size() == 1u,
|
||||||
|
"shared-region/early-break fixture has the wrong native CFG");
|
||||||
|
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||||
|
graph.unsupported_reason.c_str());
|
||||||
|
Check(graph.natural_loops.size() == 1u && graph.back_edges.size() == 1u,
|
||||||
|
"selection routing introduced a cycle around a structured loop exit");
|
||||||
|
Check(std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||||
|
return block.terminator.condition ==
|
||||||
|
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
|
||||||
|
}) == 1u,
|
||||||
|
"shared-region routing rewrote unrelated loop-control branches");
|
||||||
|
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||||
|
original_coverage,
|
||||||
|
"shared-region/early-break routing changed semantic coverage");
|
||||||
|
|
||||||
|
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||||
|
auto result = RecompileForTest(shader, options);
|
||||||
|
Check(!result.program.dispatcher_fallback &&
|
||||||
|
SpirvInstructionOpcodeCount(result.spirv, 246) == 1u &&
|
||||||
|
SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
||||||
|
"shared-region/early-break routing lost structured loop control");
|
||||||
|
CheckSpirvBinaryValidates(result.spirv);
|
||||||
|
}
|
||||||
|
|
||||||
void TestNewShaderRecompilerCfgOverlappingEarlyExitLadder() {
|
void TestNewShaderRecompilerCfgOverlappingEarlyExitLadder() {
|
||||||
const uint32_t shader[] = {
|
const uint32_t shader[] = {
|
||||||
EncodeSopc(0x06, 0, 0), // block 0
|
EncodeSopc(0x06, 0, 0), // block 0
|
||||||
@@ -12676,6 +12730,7 @@ int main() {
|
|||||||
TestNewShaderRecompilerCfgNestedTailEarlyExit();
|
TestNewShaderRecompilerCfgNestedTailEarlyExit();
|
||||||
TestNewShaderRecompilerCfgSharedReturnAfterNestedSelections();
|
TestNewShaderRecompilerCfgSharedReturnAfterNestedSelections();
|
||||||
TestNewShaderRecompilerCfgLoopSharedRegion();
|
TestNewShaderRecompilerCfgLoopSharedRegion();
|
||||||
|
TestNewShaderRecompilerCfgSharedRegionBeforeEarlyBreakLoop();
|
||||||
TestNewShaderRecompilerCfgOverlappingEarlyExitLadder();
|
TestNewShaderRecompilerCfgOverlappingEarlyExitLadder();
|
||||||
TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal();
|
TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal();
|
||||||
TestNewShaderRecompilerCfgSharedTerminalEarlyExit();
|
TestNewShaderRecompilerCfgSharedTerminalEarlyExit();
|
||||||
|
|||||||
Reference in New Issue
Block a user