[spirv-reader] kIfBreak edge counts toward divergence
This fixes the pathological cases nobody wants, and arguably
should be added to the SPIR-V spec.
If we really really want to support these cases, we can revisit.
Bug: tint:3
Change-Id: I0a75490d451676caa0933e3761098ba1fe3f8b60
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/22664
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index d91c7e5..d326ead 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -1072,8 +1072,9 @@
// There should only be one backedge per backedge block.
uint32_t num_backedges = 0;
- // Track destinations for normal forward edges, either kForward
- // or kCaseFallThrough
+ // Track destinations for normal forward edges, either kForward,
+ // kCaseFallThrough, or kIfBreak. These count toward the need
+ // to have a merge instruction.
std::vector<uint32_t> normal_forward_edges;
if (successors.empty() && src_construct.enclosing_continue) {
@@ -1190,6 +1191,14 @@
}
}
+ // The edge-kind has been finalized.
+
+ if ((edge_kind == EdgeKind::kForward) ||
+ (edge_kind == EdgeKind::kCaseFallThrough) ||
+ (edge_kind == EdgeKind::kIfBreak)) {
+ normal_forward_edges.push_back(dest);
+ }
+
if ((edge_kind == EdgeKind::kForward) ||
(edge_kind == EdgeKind::kCaseFallThrough)) {
// Check for an invalid forward exit out of this construct.
@@ -1220,8 +1229,6 @@
<< end_block_desc << " " << end_block;
}
- normal_forward_edges.push_back(dest);
-
// Check dominance.
// Look for edges that violate the dominance condition: a branch
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index f37f23f..b98eedf 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -42,6 +42,19 @@
namespace spirv {
/// Kinds of CFG edges.
+//
+// The edge kinds are used in many ways.
+//
+// For example, consider the edges leaving a basic block and going to distinct
+// targets. If the total number of kForward + kIfBreak + kCaseFallThrough edges
+// is more than 1, then the block must be a structured header, i.e. it needs
+// a merge instruction to declare the control flow divergence and associated
+// reconvergence point. Those those edge kinds count toward divergence
+// because SPIR-v is designed to easily map back to structured control flow
+// in GLSL (and C). In GLSL and C, those forward-flow edges don't have a
+// special statement to express them. The other forward edges: kSwitchBreak,
+// kLoopBreak, and kLoopContinue directly map to 'break', 'break', and
+// 'continue', respectively.
enum class EdgeKind {
// A back-edge: An edge from a node to one of its ancestors in a depth-first
// search from the entry block.
@@ -64,10 +77,7 @@
kIfBreak,
// An edge from one switch case to the next sibling switch case.
kCaseFallThrough,
- // None of the above. By structured control flow rules, there can be at most
- // one forward edge leaving a basic block. Otherwise there must have been a
- // merge instruction declaring the divergence and associated reconvergence
- // point.
+ // None of the above.
kForward
};
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index 7dfcb60..9cc8304 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -6529,8 +6529,9 @@
"a structured header (it has no merge instruction)"));
}
-TEST_F(SpvParserTest,
- FindIfSelectionInternalHeaders_IfBreak_FromThen_ForwardWithinThen) {
+TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromThen_ForwardWithinThen) {
+ // Arguably SPIR-V allows this configuration. We're debating whether to ban
+ // it.
// TODO(dneto): We can make this case work, if we injected
// if (!cond2) { rest-of-then-body }
// at block 30
@@ -6554,33 +6555,24 @@
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
- EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+ EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 99));
auto* bi20 = fe.GetBlockInfo(20);
ASSERT_NE(bi20, nullptr);
- ASSERT_NE(bi20->true_head_for, nullptr);
- EXPECT_EQ(bi20->true_head_for->begin_id, 10u);
- EXPECT_EQ(bi20->false_head_for, nullptr);
- EXPECT_EQ(bi20->premerge_head_for, nullptr);
- EXPECT_EQ(bi20->exclusive_false_head_for, nullptr);
EXPECT_EQ(bi20->succ_edge.count(80), 1u);
EXPECT_EQ(bi20->succ_edge[80], EdgeKind::kForward);
EXPECT_EQ(bi20->succ_edge.count(99), 1u);
EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
- auto* bi80 = fe.GetBlockInfo(80);
- ASSERT_NE(bi80, nullptr);
- EXPECT_EQ(bi80->true_head_for, nullptr);
- EXPECT_EQ(bi80->false_head_for, nullptr);
- EXPECT_EQ(bi80->premerge_head_for, nullptr);
- EXPECT_EQ(bi80->exclusive_false_head_for, nullptr);
- EXPECT_EQ(bi80->succ_edge.count(99), 1u);
- EXPECT_EQ(bi80->succ_edge[99], EdgeKind::kIfBreak);
+ EXPECT_THAT(p->error(),
+ Eq("Control flow diverges at block 20 (to 99, 80) but it is not "
+ "a structured header (it has no merge instruction)"));
}
-TEST_F(SpvParserTest,
- FindIfSelectionInternalHeaders_IfBreak_FromElse_ForwardWithinElse) {
+TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromElse_ForwardWithinElse) {
+ // Arguably SPIR-V allows this configuration. We're debating whether to ban
+ // it.
// TODO(dneto): We can make this case work, if we injected
// if (!cond2) { rest-of-else-body }
// at block 30
@@ -6607,33 +6599,22 @@
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
- EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
+ EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
auto* bi30 = fe.GetBlockInfo(30);
ASSERT_NE(bi30, nullptr);
- EXPECT_EQ(bi30->true_head_for, nullptr);
- ASSERT_NE(bi30->false_head_for, nullptr);
- EXPECT_EQ(bi30->false_head_for->begin_id, 10u);
- EXPECT_EQ(bi30->premerge_head_for, nullptr);
- EXPECT_EQ(bi30->exclusive_false_head_for, nullptr);
EXPECT_EQ(bi30->succ_edge.count(80), 1u);
EXPECT_EQ(bi30->succ_edge[80], EdgeKind::kForward);
EXPECT_EQ(bi30->succ_edge.count(99), 1u);
EXPECT_EQ(bi30->succ_edge[99], EdgeKind::kIfBreak);
- auto* bi80 = fe.GetBlockInfo(80);
- ASSERT_NE(bi80, nullptr);
- EXPECT_EQ(bi80->true_head_for, nullptr);
- EXPECT_EQ(bi80->false_head_for, nullptr);
- EXPECT_EQ(bi80->premerge_head_for, nullptr);
- EXPECT_EQ(bi80->exclusive_false_head_for, nullptr);
- EXPECT_EQ(bi80->succ_edge.count(99), 1u);
- EXPECT_EQ(bi80->succ_edge[99], EdgeKind::kIfBreak);
+ EXPECT_THAT(p->error(),
+ Eq("Control flow diverges at block 30 (to 99, 80) but it is not "
+ "a structured header (it has no merge instruction)"));
}
-TEST_F(SpvParserTest,
- FindIfSelectionInternalHeaders_IfBreak_WithForwardToPremerge_IsError) {
+TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_WithForwardToPremerge_IsError) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -6657,12 +6638,19 @@
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
- EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
+ EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
- EXPECT_THAT(
- p->error(),
- Eq("Block 20 in if-selection headed at block 10 branches to both the "
- "merge block 99 and also to block 80 later in the selection"));
+
+ auto* bi20 = fe.GetBlockInfo(20);
+ ASSERT_NE(bi20, nullptr);
+ EXPECT_EQ(bi20->succ_edge.count(80), 1u);
+ EXPECT_EQ(bi20->succ_edge[80], EdgeKind::kForward);
+ EXPECT_EQ(bi20->succ_edge.count(99), 1u);
+ EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
+
+ EXPECT_THAT(p->error(),
+ Eq("Control flow diverges at block 20 (to 99, 80) but it is not "
+ "a structured header (it has no merge instruction)"));
}
TEST_F(SpvParserTest, DISABLED_Codegen_IfBreak_FromThen_ForwardWithinThen) {