[spirv-reader] Find if-selection internal headers

Finds the "then", the "else", and "premerge" nodes.
The premerge node, if it exists, is the first block where
the normal forward flow of the "then" and "else" clauses
converge, but before the merge block.

Finds error case where there a block has both an if-break
edge and a forward-to-premerge.  There is no good way
to model that in a high level language.

Bug: tint:3
Change-Id: I759fc539f3480e38d091041db6a9abd15f3df769
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/21240
Reviewed-by: dan sinclair <dsinclair@google.com>
This commit is contained in:
David Neto 2020-05-07 20:27:42 +00:00
parent d8082d27a3
commit cad427e787
3 changed files with 700 additions and 14 deletions

View File

@ -451,7 +451,9 @@ bool FunctionEmitter::EmitBody() {
if (!ClassifyCFGEdges()) {
return false;
}
// TODO(dneto): FindIfSelectionHeaders
if (!FindIfSelectionInternalHeaders()) {
return false;
}
// TODO(dneto): FindInvalidNestingBetweenSelections
if (!EmitFunctionVariables()) {
@ -956,7 +958,8 @@ bool FunctionEmitter::ClassifyCFGEdges() {
// For each branch encountered, classify each edge (S,T) as:
// - a back-edge
// - a structured exit (specific ways of branching to enclosing construct)
// - a normal (forward) edge
// - a normal (forward) edge, either natural control flow or a case
// fallthrough
//
// If more than one block is targeted by a normal edge, then S must be a
// structured header.
@ -990,7 +993,8 @@ bool FunctionEmitter::ClassifyCFGEdges() {
// There should only be one backedge per backedge block.
uint32_t num_backedges = 0;
// Track destinations for normal forward edges.
// Track destinations for normal forward edges, either kForward
// or kCaseFallThrough
std::vector<uint32_t> normal_forward_edges;
if (successors.empty() && src_construct.enclosing_continue) {
@ -1167,6 +1171,128 @@ bool FunctionEmitter::ClassifyCFGEdges() {
return success();
}
bool FunctionEmitter::FindIfSelectionInternalHeaders() {
if (failed()) {
return false;
}
for (auto& construct : constructs_) {
if (construct->kind != Construct::kIfSelection) {
continue;
}
const auto* branch =
GetBlockInfo(construct->begin_id)->basic_block->terminator();
const auto true_head = branch->GetSingleWordInOperand(1);
const auto false_head = branch->GetSingleWordInOperand(2);
auto* true_head_info = GetBlockInfo(true_head);
auto* false_head_info = GetBlockInfo(false_head);
const auto true_head_pos = true_head_info->pos;
const auto false_head_pos = false_head_info->pos;
const bool contains_true = construct->ContainsPos(true_head_pos);
const bool contains_false = construct->ContainsPos(false_head_pos);
if (contains_true) {
true_head_info->true_head_for = construct.get();
}
if (contains_false) {
false_head_info->false_head_for = construct.get();
}
if ((!contains_true) && contains_false) {
false_head_info->exclusive_false_head_for = construct.get();
}
if (contains_true && contains_false && (true_head_pos != false_head_pos)) {
// This construct has both a "then" clause and an "else" clause.
//
// We have this structure:
//
// Option 1:
//
// * condbranch
// * true-head (start of then-clause)
// ...
// * end-then-clause
// * false-head (start of else-clause)
// ...
// * end-false-clause
// * premerge-head
// ...
// * selection merge
//
// Option 2:
//
// * condbranch
// * true-head (start of then-clause)
// ...
// * end-then-clause
// * false-head (start of else-clause) and also premerge-head
// ...
// * end-false-clause
// * selection merge
//
// Option 3:
//
// * condbranch
// * false-head (start of else-clause)
// ...
// * end-else-clause
// * true-head (start of then-clause) and also premerge-head
// ...
// * end-then-clause
// * selection merge
//
// The premerge-head exists if there is a kForward branch from the end
// of the first clause to a block within the surrounding selection.
// The first clause might be a then-clause or an else-clause.
const auto second_head = std::max(true_head_pos, false_head_pos);
const auto end_first_clause_pos = second_head - 1;
assert(end_first_clause_pos < block_order_.size());
const auto end_first_clause = block_order_[end_first_clause_pos];
uint32_t premerge_id = 0;
uint32_t if_break_id = 0;
for (auto& then_succ_iter : GetBlockInfo(end_first_clause)->succ_edge) {
const uint32_t dest_id = then_succ_iter.first;
const auto edge_kind = then_succ_iter.second;
switch (edge_kind) {
case EdgeKind::kIfBreak:
if_break_id = dest_id;
break;
case EdgeKind::kForward: {
if (construct->ContainsPos(GetBlockInfo(dest_id)->pos)) {
// It's a premerge.
if (premerge_id != 0) {
// TODO(dneto): I think this is impossible to trigger at this
// point in the flow. It would require a merge instruction to
// get past the check of "at-most-one-forward-edge".
return Fail()
<< "invalid structure: then-clause headed by block "
<< true_head << " ending at block " << end_first_clause
<< " has two forward edges to within selection"
<< " going to " << premerge_id << " and " << dest_id;
}
premerge_id = dest_id;
GetBlockInfo(dest_id)->premerge_head_for = construct.get();
}
break;
}
default:
break;
}
}
if (if_break_id != 0 && premerge_id != 0) {
return Fail() << "Block " << end_first_clause
<< " in if-selection headed at block "
<< construct->begin_id
<< " branches to both the merge block " << if_break_id
<< " and also to block " << premerge_id
<< " later in the selection";
}
}
}
return success();
}
bool FunctionEmitter::EmitFunctionVariables() {
if (failed()) {
return false;

View File

@ -101,6 +101,9 @@ struct BlockInfo {
/// The immediately enclosing structured construct.
const Construct* construct = nullptr;
/// Maps the ID of a successor block (in the CFG) to its edge classification.
std::unordered_map<uint32_t, EdgeKind> succ_edge;
/// The following fields record relationships among blocks in a selection
/// construct for an OpSwitch instruction.
@ -118,8 +121,24 @@ struct BlockInfo {
/// The list of switch values that cause a branch to this block.
std::unique_ptr<std::vector<uint64_t>> case_values;
/// Maps the ID of a successor block (in the CFG) to its edge classification.
std::unordered_map<uint32_t, EdgeKind> succ_edge;
/// The following fields record relationships among blocks in a selection
/// construct for an OpBranchConditional instruction.
/// If not null, then the pointed-at construct is a selection for an
/// OpBranchConditional and this block is the "true" target for it. We say
/// this block "heads" the true case.
const Construct* true_head_for = nullptr;
/// If not null, then the pointed-at construct is a selection for an
/// OpBranchConditional and this block is the "false" target for it. We say
/// this block "heads" the false case.
const Construct* false_head_for = nullptr;
/// If not null, then the pointed-at construct is the first block at which
/// control reconverges between the "then" and "else" clauses, but before
/// the merge block for that selection.
const Construct* premerge_head_for = nullptr;
/// The construct for which this block is the false head, and that construct
/// does not have a true head.
const Construct* exclusive_false_head_for = nullptr;
};
inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) {
@ -228,6 +247,16 @@ class FunctionEmitter {
/// @returns false on failure
bool ClassifyCFGEdges();
/// Marks the blocks within a selection construct that are the first blocks
/// in the "then" clause, the "else" clause, and the "premerge" clause.
/// The head of the premerge clause is the block, if it exists, at which
/// control flow reconverges from the "then" and "else" clauses, but before
/// before the merge block for that selection. The existence of a premerge
/// should be an exceptional case, but is allowed by the structured control
/// flow rules.
/// @returns false if bad nesting has been detected.
bool FindIfSelectionInternalHeaders();
/// Emits declarations of function variables.
/// @returns false if emission failed.
bool EmitFunctionVariables();

View File

@ -73,6 +73,14 @@ bool FlowClassifyCFGEdges(FunctionEmitter* fe) {
return fe->ClassifyCFGEdges();
}
/// Runs the necessary flow until and including finding if-selection
/// internal headers.
/// @returns the result of classify CFG edges.
bool FlowFindIfSelectionInternalHeaders(FunctionEmitter* fe) {
EXPECT_TRUE(FlowClassifyCFGEdges(fe)) << fe->parser()->error();
return fe->FindIfSelectionInternalHeaders();
}
TEST_F(SpvParserTest, TerminatorsAreSane_SingleBlock) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@ -4571,7 +4579,7 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromIfHeader) {
auto* bi = fe.GetBlockInfo(20);
ASSERT_NE(bi, nullptr);
EXPECT_EQ(bi->succ_edge.count(99), 1);
EXPECT_EQ(bi->succ_edge.count(99), 1u);
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kIfBreak);
}
@ -4600,13 +4608,13 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromIfThenElse) {
// Then clause
auto* bi20 = fe.GetBlockInfo(20);
ASSERT_NE(bi20, nullptr);
EXPECT_EQ(bi20->succ_edge.count(99), 1);
EXPECT_EQ(bi20->succ_edge.count(99), 1u);
EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
// Else clause
auto* bi50 = fe.GetBlockInfo(50);
ASSERT_NE(bi50, nullptr);
EXPECT_EQ(bi50->succ_edge.count(99), 1);
EXPECT_EQ(bi50->succ_edge.count(99), 1u);
EXPECT_EQ(bi50->succ_edge[99], EdgeKind::kIfBreak);
}
@ -4741,7 +4749,7 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromNestedIf_Unconditional) {
auto* bi = fe.GetBlockInfo(30);
ASSERT_NE(bi, nullptr);
EXPECT_EQ(bi->succ_edge.count(99), 1);
EXPECT_EQ(bi->succ_edge.count(99), 1u);
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
}
@ -4773,7 +4781,7 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromNestedIf_Conditional) {
auto* bi = fe.GetBlockInfo(30);
ASSERT_NE(bi, nullptr);
EXPECT_EQ(bi->succ_edge.count(99), 1);
EXPECT_EQ(bi->succ_edge.count(99), 1u);
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
}
@ -4973,7 +4981,7 @@ TEST_F(SpvParserTest,
auto* bi = fe.GetBlockInfo(40);
ASSERT_NE(bi, nullptr);
EXPECT_EQ(bi->succ_edge.count(99), 1);
EXPECT_EQ(bi->succ_edge.count(99), 1u);
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
}
@ -5012,7 +5020,7 @@ TEST_F(SpvParserTest,
auto* bi = fe.GetBlockInfo(40);
ASSERT_NE(bi, nullptr);
EXPECT_EQ(bi->succ_edge.count(99), 1);
EXPECT_EQ(bi->succ_edge.count(99), 1u);
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
}
@ -6011,7 +6019,7 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_Pathological_Forward_Premerge) {
auto* bi60 = fe.GetBlockInfo(60);
ASSERT_NE(bi60, nullptr);
EXPECT_EQ(bi60->succ_edge.count(99), 1);
EXPECT_EQ(bi60->succ_edge.count(99), 1u);
EXPECT_EQ(bi60->succ_edge[99], EdgeKind::kIfBreak);
}
@ -6042,10 +6050,533 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_Pathological_Forward_Regardless) {
auto* bi20 = fe.GetBlockInfo(20);
ASSERT_NE(bi20, nullptr);
EXPECT_EQ(bi20->succ_edge.count(99), 1);
EXPECT_EQ(bi20->succ_edge.count(99), 1u);
EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
}
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_NoIf) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
auto* bi = fe.GetBlockInfo(10);
ASSERT_NE(bi, nullptr);
EXPECT_EQ(bi->true_head_for, nullptr);
EXPECT_EQ(bi->false_head_for, nullptr);
EXPECT_EQ(bi->premerge_head_for, nullptr);
EXPECT_EQ(bi->premerge_head_for, nullptr);
EXPECT_EQ(bi->exclusive_false_head_for, nullptr);
}
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_ThenElse) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %30
%20 = OpLabel
OpBranch %99
%30 = OpLabel
OpBranch %99
%99 = OpLabel
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
auto* bi20 = fe.GetBlockInfo(20);
ASSERT_NE(bi20, nullptr);
ASSERT_NE(bi20->true_head_for, nullptr);
EXPECT_EQ(bi20->true_head_for->begin_id, 10);
EXPECT_EQ(bi20->false_head_for, nullptr);
EXPECT_EQ(bi20->premerge_head_for, nullptr);
EXPECT_EQ(bi20->exclusive_false_head_for, nullptr);
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, 10);
EXPECT_EQ(bi30->premerge_head_for, nullptr);
EXPECT_EQ(bi30->exclusive_false_head_for, nullptr);
}
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_IfOnly) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %30 %99
%30 = OpLabel
OpBranch %99
%99 = OpLabel
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
auto* bi30 = fe.GetBlockInfo(30);
ASSERT_NE(bi30, nullptr);
ASSERT_NE(bi30->true_head_for, nullptr);
EXPECT_EQ(bi30->true_head_for->begin_id, 10);
EXPECT_EQ(bi30->false_head_for, nullptr);
EXPECT_EQ(bi30->premerge_head_for, nullptr);
EXPECT_EQ(bi30->exclusive_false_head_for, nullptr);
}
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_ElseOnly) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %99 %30
%30 = OpLabel
OpBranch %99
%99 = OpLabel
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
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, 10);
EXPECT_EQ(bi30->premerge_head_for, nullptr);
ASSERT_NE(bi30->exclusive_false_head_for, nullptr);
EXPECT_EQ(bi30->exclusive_false_head_for->begin_id, 10);
}
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_Regardless) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %20 ; same target
%20 = OpLabel
OpBranch %80
%80 = OpLabel
OpBranch %99
%99 = OpLabel
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&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, 10);
ASSERT_NE(bi20->false_head_for, nullptr);
EXPECT_EQ(bi20->false_head_for->begin_id, 10);
EXPECT_EQ(bi20->premerge_head_for, nullptr);
EXPECT_EQ(bi20->exclusive_false_head_for, nullptr);
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);
}
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_Premerge_Simple) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %30
%20 = OpLabel
OpBranch %80
%30 = OpLabel
OpBranch %80
%80 = OpLabel ; premerge node
OpBranch %99
%99 = OpLabel
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
auto* bi80 = fe.GetBlockInfo(80);
ASSERT_NE(bi80, nullptr);
EXPECT_EQ(bi80->true_head_for, nullptr);
EXPECT_EQ(bi80->false_head_for, nullptr);
ASSERT_NE(bi80->premerge_head_for, nullptr);
EXPECT_EQ(bi80->premerge_head_for->begin_id, 10);
EXPECT_EQ(bi80->exclusive_false_head_for, nullptr);
}
TEST_F(SpvParserTest,
FindIfSelectionInternalHeaders_Premerge_ThenDirectToElse) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %30
%20 = OpLabel
OpBranch %30
%30 = OpLabel
OpBranch %80
%80 = OpLabel ; premerge node
OpBranch %99
%99 = OpLabel
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 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, 10);
EXPECT_EQ(bi20->false_head_for, nullptr);
EXPECT_EQ(bi20->premerge_head_for, nullptr);
EXPECT_EQ(bi20->exclusive_false_head_for, nullptr);
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, 10);
ASSERT_NE(bi30->premerge_head_for, nullptr);
EXPECT_EQ(bi30->premerge_head_for->begin_id, 10);
EXPECT_EQ(bi30->exclusive_false_head_for, nullptr);
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);
}
TEST_F(SpvParserTest,
FindIfSelectionInternalHeaders_Premerge_ElseDirectToThen) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %30
%20 = OpLabel
OpBranch %80 ; branches to premerge
%30 = OpLabel ; else
OpBranch %20 ; branches to then
%80 = OpLabel ; premerge node
OpBranch %99
%99 = OpLabel
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 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, 10);
EXPECT_EQ(bi20->false_head_for, nullptr);
ASSERT_NE(bi20->premerge_head_for, nullptr);
EXPECT_EQ(bi20->premerge_head_for->begin_id, 10);
EXPECT_EQ(bi20->exclusive_false_head_for, nullptr);
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, 10);
EXPECT_EQ(bi30->premerge_head_for, nullptr);
EXPECT_EQ(bi30->exclusive_false_head_for, nullptr);
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);
}
TEST_F(SpvParserTest,
FindIfSelectionInternalHeaders_Premerge_MultiCandidate_Error) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %30
%20 = OpLabel
; Try to force several branches down into "else" territory,
; but we error out earlier in the flow due to lack of merge
; instruction.
OpBranchConditional %cond2 %70 %80
%30 = OpLabel
OpBranch %70
%70 = OpLabel ; candidate premerge
OpBranch %80
%80 = OpLabel ; canddiate premerge
OpBranch %99
%99 = OpLabel
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
// Error out sooner in the flow
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
EXPECT_THAT(p->error(),
Eq("Control flow diverges at block 20 (to 70, 80) but it is not "
"a structured header (it has no merge instruction)"));
}
TEST_F(SpvParserTest,
FindIfSelectionInternalHeaders_IfBreak_FromThen_ForwardWithinThen) {
// TODO(dneto): We can make this case work, if we injected
// if (!cond2) { rest-of-then-body }
// at block 30
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %99
%20 = OpLabel
OpBranchConditional %cond2 %99 %80 ; break with forward edge
%80 = OpLabel ; still in then clause
OpBranch %99
%99 = OpLabel
OpReturn
OpFunctionEnd
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&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, 10);
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);
}
TEST_F(SpvParserTest,
FindIfSelectionInternalHeaders_IfBreak_FromElse_ForwardWithinElse) {
// TODO(dneto): We can make this case work, if we injected
// if (!cond2) { rest-of-else-body }
// at block 30
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %30
%20 = OpLabel
OpBranch %99
%30 = OpLabel ; else clause
OpBranchConditional %cond2 %99 %80 ; break with forward edge
%80 = OpLabel ; still in then clause
OpBranch %99
%99 = OpLabel
OpReturn
OpFunctionEnd
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&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, 10);
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);
}
TEST_F(SpvParserTest,
FindIfSelectionInternalHeaders_IfBreak_WithForwardToPremerge_Error) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %30
%20 = OpLabel ; then
OpBranchConditional %cond2 %99 %80 ; break with forward to premerge is error
%30 = OpLabel ; else
OpBranch %80
%80 = OpLabel ; premerge node
OpBranch %99
%99 = OpLabel
OpReturn
OpFunctionEnd
)";
auto* p = parser(test::Assemble(assembly));
std::cout << assembly;
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&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"));
}
TEST_F(SpvParserTest, DISABLED_Codegen_IfBreak_FromThen_ForwardWithinThen) {
// TODO(dneto): We can make this case work, if we injected
// if (!cond2) { rest-of-then-body }
// at block 30
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %99
%20 = OpLabel
OpBranchConditional %cond2 %99 %80 ; break with forward edge
%80 = OpLabel ; still in then clause
OpBranch %99
%99 = OpLabel
OpReturn
OpFunctionEnd
)";
}
TEST_F(SpvParserTest, DISABLED_Codegen_IfBreak_FromElse_ForwardWithinElse) {
// TODO(dneto): We can make this case work, if we injected
// if (!cond2) { rest-of-else-body }
// at block 30
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %30
%20 = OpLabel
OpBranch %99
%30 = OpLabel ; else clause
OpBranchConditional %cond2 %99 %80 ; break with forward edge
%80 = OpLabel ; still in then clause
OpBranch %99
%99 = OpLabel
OpReturn
OpFunctionEnd
)";
}
} // namespace
} // namespace spirv
} // namespace reader