rsx/cfg: Handle nested IF/LOOP blocks falling out to unsuitable nodes (ELSE).

- In that case, find the node's END node and link to that instead. ELSE nodes are not reachable from children of the preceding IF.
- ELSE nodes are special this way, all other types of nodes are reachable by adjacency.
This commit is contained in:
kd-11 2025-12-07 18:16:04 +03:00 committed by kd-11
parent 26fd0510ab
commit d23ea4760b
3 changed files with 83 additions and 3 deletions

View File

@ -74,8 +74,19 @@ namespace rsx::assembler
{
if (auto found = find_block_for_pc(id))
{
parent->insert_succ(found, edge_type);
found->insert_pred(parent, edge_type);
auto succ = found;
if (found->is_of_type(EdgeType::ELSE) &&
(edge_type == EdgeType::ENDIF || edge_type == EdgeType::ENDLOOP))
{
// If we landed on an "ELSE" node, link to its "ENDIF" counterpart
auto if_parent = found->pred.front().from;
auto endif_edge = std::find_if(if_parent->succ.begin(), if_parent->succ.end(), FN(x.type == EdgeType::ENDIF));
ensure(endif_edge != if_parent->succ.end(), "CFG: Invalid ELSE node");
succ = endif_edge->to;
}
parent->insert_succ(succ, edge_type);
succ->insert_pred(parent, edge_type);
return found;
}
@ -109,7 +120,7 @@ namespace rsx::assembler
case EdgeType::IF:
case EdgeType::ELSE:
{
// Find the merge node from the parent
// Find the merge node from the parent.
auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDIF));
ensure(succ != parent->succ.end(), "CFG: Broken IF linkage. Please report to developers.");
bb->insert_succ(succ->to, EdgeType::ENDIF);

View File

@ -91,6 +91,7 @@ namespace rsx::assembler
struct BasicBlock
{
u32 id = 0;
std::vector<Instruction> instructions; // Program instructions for the RSX processor
std::vector<FlowEdge> succ; // Forward edges. Sorted closest first.
std::vector<FlowEdge> pred; // Back edges. Sorted closest first.

View File

@ -404,4 +404,72 @@ namespace rsx::assembler
EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_x, 2);
EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_y, 3);
}
TEST(TestFPIR, RegisterDependencyPass_Complex_IF_ELSE_Simpsons)
{
// Complex IF-ELSE nest observed in Simpson's game. Rewritten for simplicity.
// There is no tail block. No epilogues should be injected in this scenario since H4 (the trigger) is defined on all branches.
// R2 is indeed clobbered but the outer ELSE branch should not be able to see the inner IF-ELSE blocks as predecessors.
auto ir = FPIR::from_source(R"(
MOV R2, #{ 0.25 };
IF.GT;
SLT R4, H2, #{ 0.125 };
IF.GT;
ADD H2, H0, H3;
FMA H4, R2, H2, H3;
ELSE;
MOV H2, #{ 0.125 };
ADD H0, H0, H2;
FMA H4, R2, H2, H3;
ENDIF;
ELSE;
FMA H4, R2, H2, H3;
MOV H0, H4;
ENDIF;
)");
auto bytecode = ir.compile();
RSXFragmentProgram prog{};
prog.data = bytecode.data();
auto graph = deconstruct_fragment_program(prog);
ASSERT_EQ(graph.blocks.size(), 6);
FP::RegisterAnnotationPass annotation_pass{ prog };
FP::RegisterDependencyPass deps_pass{};
annotation_pass.run(graph);
deps_pass.run(graph);
const BasicBlock
*bb0 = get_graph_block(graph, 0),
*bb1 = get_graph_block(graph, 1),
*bb2 = get_graph_block(graph, 2),
*bb3 = get_graph_block(graph, 3),
*bb4 = get_graph_block(graph, 4),
*bb5 = get_graph_block(graph, 5);
// Sanity
EXPECT_EQ(bb0->instructions.size(), 2);
EXPECT_EQ(bb1->instructions.size(), 2);
EXPECT_EQ(bb2->instructions.size(), 2);
EXPECT_EQ(bb3->instructions.size(), 3);
EXPECT_EQ(bb4->instructions.size(), 2);
EXPECT_EQ(bb5->instructions.size(), 0); // Phi/Merge only.
// Nested children must recursively fall out to the closest ENDIF
ASSERT_EQ(bb4->pred.size(), 1);
EXPECT_EQ(bb4->pred.front().type, EdgeType::ELSE);
EXPECT_EQ(bb5->pred.size(), 4); // 2 IF and 2 ELSE paths exist
// Check that we get no epilogues
EXPECT_EQ(bb0->epilogue.size(), 0);
EXPECT_EQ(bb1->epilogue.size(), 0);
EXPECT_EQ(bb2->epilogue.size(), 0);
EXPECT_EQ(bb3->epilogue.size(), 0);
EXPECT_EQ(bb4->epilogue.size(), 0);
EXPECT_EQ(bb5->epilogue.size(), 0);
}
}