[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>
This commit is contained in:
David Neto 2020-06-08 14:37:05 +00:00 committed by dan sinclair
parent 7615d9468c
commit 5d4c35f96e
3 changed files with 52 additions and 47 deletions

View File

@ -1072,8 +1072,9 @@ bool FunctionEmitter::ClassifyCFGEdges() {
// 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 @@ bool FunctionEmitter::ClassifyCFGEdges() {
}
}
// 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 @@ bool FunctionEmitter::ClassifyCFGEdges() {
<< end_block_desc << " " << end_block;
}
normal_forward_edges.push_back(dest);
// Check dominance.
// Look for edges that violate the dominance condition: a branch

View File

@ -42,6 +42,19 @@ namespace reader {
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 @@ enum class EdgeKind {
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
};

View File

@ -6529,8 +6529,9 @@ TEST_F(SpvParserTest,
"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 @@ TEST_F(SpvParserTest,
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 @@ TEST_F(SpvParserTest,
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 @@ TEST_F(SpvParserTest,
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) {