[spirv-reader] Classify kSwitchBreak from deep in control flow

This also refactors break detection.

Bug: tint:3
Change-Id: I3a3e01c8d76d7c6fc2a14b3dbff136acd487e802
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/21220
Reviewed-by: dan sinclair <dsinclair@google.com>
This commit is contained in:
David Neto 2020-05-07 20:20:05 +00:00
parent 4dfda012b2
commit 15fd7366cc
5 changed files with 360 additions and 75 deletions

View File

@ -26,18 +26,36 @@ Construct::Construct(const Construct* the_parent,
uint32_t the_begin_pos, uint32_t the_begin_pos,
uint32_t the_end_pos) uint32_t the_end_pos)
: parent(the_parent), : parent(the_parent),
enclosing_loop(
// Compute the enclosing loop construct. Doing this in the
// constructor member list lets us make the member const.
// Compare parent depth because loop and continue are siblings and
// it's incidental which will appear on the stack first.
the_kind == kLoop
? this
: ((parent && parent->depth < the_depth) ? parent->enclosing_loop
: nullptr)),
enclosing_continue( enclosing_continue(
// Compute the enclosing continue construct. Doing this in the // Compute the enclosing continue construct. Doing this in the
// constructor member list lets us make the member const. // constructor member list lets us make the member const.
the_kind == kContinue // Compare parent depth because loop and continue are siblings and
// it's incidental which will appear on the stack first.
the_kind == kContinue ? this
: ((parent && parent->depth < the_depth)
? parent->enclosing_continue
: nullptr)),
enclosing_loop_or_continue_or_switch(
// Compute the enclosing loop or continue or switch construct.
// Doing this in the constructor member list lets us make the
// member const.
// Compare parent depth because loop and continue are siblings and
// it's incidental which will appear on the stack first.
(the_kind == kLoop || the_kind == kContinue ||
the_kind == kSwitchSelection)
? this ? this
: (parent ? parent->enclosing_continue : nullptr)), : ((parent && parent->depth < the_depth)
enclosing_loop_or_continue( ? parent->enclosing_loop_or_continue_or_switch
// Compute the enclosing loop or continue construct. Doing this : nullptr)),
// in the constructor member list lets us make the member const.
(the_kind == kLoop || the_kind == kContinue)
? this
: (parent ? parent->enclosing_loop_or_continue : nullptr)),
depth(the_depth), depth(the_depth),
kind(the_kind), kind(the_kind),
begin_id(the_begin_id), begin_id(the_begin_id),

View File

@ -68,12 +68,18 @@ struct Construct {
/// The nearest enclosing construct (other than itself), or nullptr if /// The nearest enclosing construct (other than itself), or nullptr if
/// this construct represents the entire function. /// this construct represents the entire function.
const Construct* const parent = nullptr; const Construct* const parent = nullptr;
/// The nearest continue construct, if one exists. A construct encloses /// The nearest enclosing loop construct, if one exists. A construct
/// itself. /// encloses itself.
const Construct* const enclosing_loop = nullptr;
/// The nearest enclosing continue construct, if one exists. A construct
/// encloses itself.
const Construct* const enclosing_continue = nullptr; const Construct* const enclosing_continue = nullptr;
/// The nearest enclosing loop or continue construct, if one exists. /// The nearest enclosing loop construct or continue construct or
/// switch-selection construct, if one exists. The signficance is
/// that a high level language "break" will branch to the merge block
/// of such an enclosing construct.
/// A construct encloses itself. /// A construct encloses itself.
const Construct* const enclosing_loop_or_continue = nullptr; const Construct* const enclosing_loop_or_continue_or_switch = nullptr;
/// Control flow nesting depth. The entry block is at nesting depth 0. /// Control flow nesting depth. The entry block is at nesting depth 0.
const int depth = 0; const int depth = 0;
@ -136,12 +142,17 @@ inline std::ostream& operator<<(std::ostream& o, const Construct& c) {
o << " parent:" << ToStringBrief(c.parent); o << " parent:" << ToStringBrief(c.parent);
if (c.enclosing_loop) {
o << " in-l:" << ToStringBrief(c.enclosing_loop);
}
if (c.enclosing_continue) { if (c.enclosing_continue) {
o << " in-c:" << ToStringBrief(c.enclosing_continue); o << " in-c:" << ToStringBrief(c.enclosing_continue);
} }
if (c.enclosing_loop_or_continue != c.enclosing_continue) { if ((c.enclosing_loop_or_continue_or_switch != c.enclosing_loop) &&
o << " in-c-l:" << ToStringBrief(c.enclosing_loop_or_continue); (c.enclosing_loop_or_continue_or_switch != c.enclosing_continue)) {
o << " in-c-l-s:" << ToStringBrief(c.enclosing_loop_or_continue_or_switch);
} }
o << " }"; o << " }";

View File

@ -927,13 +927,20 @@ bool FunctionEmitter::FindSwitchCaseHeaders() {
return success(); return success();
} }
BlockInfo* FunctionEmitter::HeaderForLoopOrContinue(const Construct* c) { BlockInfo* FunctionEmitter::HeaderIfBreakable(const Construct* c) {
if (c->kind == Construct::kLoop) { if (c == nullptr) {
return GetBlockInfo(c->begin_id); return nullptr;
} }
if (c->kind == Construct::kContinue) { switch (c->kind) {
auto* continue_block = GetBlockInfo(c->begin_id); case Construct::kLoop:
return GetBlockInfo(continue_block->header_for_continue); case Construct::kSwitchSelection:
return GetBlockInfo(c->begin_id);
case Construct::kContinue: {
const auto* continue_target = GetBlockInfo(c->begin_id);
return GetBlockInfo(continue_target->header_for_continue);
}
default:
break;
} }
return nullptr; return nullptr;
} }
@ -1051,44 +1058,42 @@ bool FunctionEmitter::ClassifyCFGEdges() {
// Check valid structured exit cases. // Check valid structured exit cases.
const auto& header_info = *GetBlockInfo(src_construct.begin_id); if (edge_kind == EdgeKind::kForward) {
if (dest == header_info.merge_for_header) { // Check for a 'break' from a loop or from a switch.
// Branch to construct's merge block. const auto* breakable_header = HeaderIfBreakable(
const bool src_is_loop_header = header_info.continue_for_header != 0; src_construct.enclosing_loop_or_continue_or_switch);
const bool src_is_continue_header = if (breakable_header != nullptr) {
header_info.header_for_continue != 0; if (dest == breakable_header->merge_for_header) {
if (src_is_loop_header || src_is_continue_header) { // It's a break.
edge_kind = EdgeKind::kLoopBreak; edge_kind = (breakable_header->construct->kind ==
} else if (src_construct.kind == Construct::kSwitchSelection) { Construct::kSwitchSelection)
edge_kind = EdgeKind::kSwitchBreak; ? EdgeKind::kSwitchBreak
} else { : EdgeKind::kLoopBreak;
edge_kind = EdgeKind::kToMerge;
}
} else {
const auto* loop_or_continue_construct =
src_construct.enclosing_loop_or_continue;
if (loop_or_continue_construct != nullptr) {
// Check for break block or continue block.
const auto* loop_header_info =
HeaderForLoopOrContinue(loop_or_continue_construct);
if (loop_header_info == nullptr) {
return Fail() << "internal error: invalid construction of loop "
"related to block "
<< loop_or_continue_construct->begin_id;
} }
if (dest == loop_header_info->merge_for_header) { }
// It's a break block for the innermost loop. }
edge_kind = EdgeKind::kLoopBreak;
} else if (dest == loop_header_info->continue_for_header) { if (edge_kind == EdgeKind::kForward) {
// It's a continue block for the innermost loop construct. // Check for a 'continue' from within a loop.
// In this case loop_or_continue_construct can't be a continue const auto* loop_header =
// construct, because then the branch to the continue target is HeaderIfBreakable(src_construct.enclosing_loop);
// a backedge, and this code is only looking at forward edges. if (loop_header != nullptr) {
if (dest == loop_header->continue_for_header) {
// It's a continue.
edge_kind = EdgeKind::kLoopContinue; edge_kind = EdgeKind::kLoopContinue;
} }
} }
} }
if (edge_kind == EdgeKind::kForward) {
const auto& header_info = *GetBlockInfo(src_construct.begin_id);
if (dest == header_info.merge_for_header) {
// Branch to construct's merge block. The loop break and
// switch break cases have already been covered.
edge_kind = EdgeKind::kToMerge;
}
}
// A forward edge into a case construct that comes from something // A forward edge into a case construct that comes from something
// other than the OpSwitch is actually a fallthrough. // other than the OpSwitch is actually a fallthrough.
if (edge_kind == EdgeKind::kForward) { if (edge_kind == EdgeKind::kForward) {

View File

@ -289,12 +289,17 @@ class FunctionEmitter {
ast::type::Type* GetVariableStoreType( ast::type::Type* GetVariableStoreType(
const spvtools::opt::Instruction& var_decl_inst); const spvtools::opt::Instruction& var_decl_inst);
/// Finds the loop header block for a loop construct or continue construct. /// Finds the header block for a structured construct that we can "break"
/// The loop header block is the block with the corresponding OpLoopMerge /// out from, from deeply nested control flow, if such a block exists.
/// instruction. /// If the construct is:
/// @param c the loop or continue construct /// - a switch selection: return the selection header (ending in OpSwitch)
/// @returns the block info for the loop header. /// - a loop construct: return the loop header block
BlockInfo* HeaderForLoopOrContinue(const Construct* c); /// - a continue construct: return the loop header block
/// Otherwise, return nullptr.
/// @param c a structured construct, or nullptr
/// @returns the block info for the structured header we can "break" from,
/// or nullptr
BlockInfo* HeaderIfBreakable(const Construct* c);
ParserImpl& parser_impl_; ParserImpl& parser_impl_;
ast::Module& ast_module_; ast::Module& ast_module_;

View File

@ -2938,7 +2938,7 @@ TEST_F(SpvParserTest, LabelControlFlowConstructs_SwitchSelection) {
EXPECT_EQ(constructs.size(), 2u); EXPECT_EQ(constructs.size(), 2u);
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{ EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null } Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
Construct{ SwitchSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 } Construct{ SwitchSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 in-c-l-s:SwitchSelection@10 }
})")) << constructs; })")) << constructs;
// The block records the nearest enclosing construct. // The block records the nearest enclosing construct.
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get()); EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
@ -3018,12 +3018,10 @@ TEST_F(SpvParserTest, LabelControlFlowConstructs_MultiBlockLoop) {
fe.RegisterMerges(); fe.RegisterMerges();
EXPECT_TRUE(fe.LabelControlFlowConstructs()); EXPECT_TRUE(fe.LabelControlFlowConstructs());
const auto& constructs = fe.constructs(); const auto& constructs = fe.constructs();
// A single-block loop consists *only* of a continue target with one block in
// it.
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{ EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null } Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
Construct{ Continue [3,5) begin_id:40 end_id:99 depth:1 parent:Function@10 in-c:Continue@40 } Construct{ Continue [3,5) begin_id:40 end_id:99 depth:1 parent:Function@10 in-c:Continue@40 }
Construct{ Loop [1,3) begin_id:20 end_id:40 depth:1 parent:Function@10 in-c-l:Loop@20 } Construct{ Loop [1,3) begin_id:20 end_id:40 depth:1 parent:Function@10 in-l:Loop@20 }
})")) << constructs; })")) << constructs;
// The block records the nearest enclosing construct. // The block records the nearest enclosing construct.
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get()); EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
@ -3119,7 +3117,7 @@ TEST_F(SpvParserTest,
Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null } Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
Construct{ IfSelection [0,2) begin_id:10 end_id:50 depth:1 parent:Function@10 } Construct{ IfSelection [0,2) begin_id:10 end_id:50 depth:1 parent:Function@10 }
Construct{ Continue [3,4) begin_id:60 end_id:99 depth:1 parent:Function@10 in-c:Continue@60 } Construct{ Continue [3,4) begin_id:60 end_id:99 depth:1 parent:Function@10 in-c:Continue@60 }
Construct{ Loop [2,3) begin_id:50 end_id:60 depth:1 parent:Function@10 in-c-l:Loop@50 } Construct{ Loop [2,3) begin_id:50 end_id:60 depth:1 parent:Function@10 in-l:Loop@50 }
})")) << constructs; })")) << constructs;
// The block records the nearest enclosing construct. // The block records the nearest enclosing construct.
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get()); EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
@ -3237,9 +3235,9 @@ TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_Switch_If) {
// The ordering among siblings depends on the computed block order. // The ordering among siblings depends on the computed block order.
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{ EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null } Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null }
Construct{ SwitchSelection [0,7) begin_id:10 end_id:99 depth:1 parent:Function@10 } Construct{ SwitchSelection [0,7) begin_id:10 end_id:99 depth:1 parent:Function@10 in-c-l-s:SwitchSelection@10 }
Construct{ IfSelection [1,3) begin_id:50 end_id:89 depth:2 parent:SwitchSelection@10 } Construct{ IfSelection [1,3) begin_id:50 end_id:89 depth:2 parent:SwitchSelection@10 in-c-l-s:SwitchSelection@10 }
Construct{ IfSelection [4,6) begin_id:20 end_id:49 depth:2 parent:SwitchSelection@10 } Construct{ IfSelection [4,6) begin_id:20 end_id:49 depth:2 parent:SwitchSelection@10 in-c-l-s:SwitchSelection@10 }
})")) << constructs; })")) << constructs;
// The block records the nearest enclosing construct. // The block records the nearest enclosing construct.
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get()); EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
@ -3287,7 +3285,7 @@ TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_If_Switch) {
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{ EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null } Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
Construct{ IfSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 } Construct{ IfSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 }
Construct{ SwitchSelection [1,3) begin_id:20 end_id:89 depth:2 parent:IfSelection@10 } Construct{ SwitchSelection [1,3) begin_id:20 end_id:89 depth:2 parent:IfSelection@10 in-c-l-s:SwitchSelection@20 }
})")) << constructs; })")) << constructs;
// The block records the nearest enclosing construct. // The block records the nearest enclosing construct.
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get()); EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
@ -3341,8 +3339,8 @@ TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_Loop_Loop) {
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{ EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null } Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null }
Construct{ Continue [4,6) begin_id:50 end_id:89 depth:1 parent:Function@10 in-c:Continue@50 } Construct{ Continue [4,6) begin_id:50 end_id:89 depth:1 parent:Function@10 in-c:Continue@50 }
Construct{ Loop [1,4) begin_id:20 end_id:50 depth:1 parent:Function@10 in-c-l:Loop@20 } Construct{ Loop [1,4) begin_id:20 end_id:50 depth:1 parent:Function@10 in-l:Loop@20 }
Construct{ Continue [2,3) begin_id:30 end_id:40 depth:2 parent:Loop@20 in-c:Continue@30 } Construct{ Continue [2,3) begin_id:30 end_id:40 depth:2 parent:Loop@20 in-l:Loop@20 in-c:Continue@30 }
})")) << constructs; })")) << constructs;
// The block records the nearest enclosing construct. // The block records the nearest enclosing construct.
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get()); EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
@ -3396,8 +3394,8 @@ TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_Loop_If) {
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{ EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null } Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null }
Construct{ Continue [5,6) begin_id:80 end_id:99 depth:1 parent:Function@10 in-c:Continue@80 } Construct{ Continue [5,6) begin_id:80 end_id:99 depth:1 parent:Function@10 in-c:Continue@80 }
Construct{ Loop [1,5) begin_id:20 end_id:80 depth:1 parent:Function@10 in-c-l:Loop@20 } Construct{ Loop [1,5) begin_id:20 end_id:80 depth:1 parent:Function@10 in-l:Loop@20 }
Construct{ IfSelection [2,4) begin_id:30 end_id:49 depth:2 parent:Loop@20 in-c-l:Loop@20 } Construct{ IfSelection [2,4) begin_id:30 end_id:49 depth:2 parent:Loop@20 in-l:Loop@20 }
})")) << constructs; })")) << constructs;
// The block records the nearest enclosing construct. // The block records the nearest enclosing construct.
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get()); EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
@ -3447,7 +3445,7 @@ TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_LoopContinue_If) {
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{ EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null } Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
Construct{ Continue [2,5) begin_id:30 end_id:99 depth:1 parent:Function@10 in-c:Continue@30 } Construct{ Continue [2,5) begin_id:30 end_id:99 depth:1 parent:Function@10 in-c:Continue@30 }
Construct{ Loop [1,2) begin_id:20 end_id:30 depth:1 parent:Function@10 in-c-l:Loop@20 } Construct{ Loop [1,2) begin_id:20 end_id:30 depth:1 parent:Function@10 in-l:Loop@20 }
Construct{ IfSelection [2,4) begin_id:30 end_id:49 depth:2 parent:Continue@30 in-c:Continue@30 } Construct{ IfSelection [2,4) begin_id:30 end_id:49 depth:2 parent:Continue@30 in-c:Continue@30 }
})")) << constructs; })")) << constructs;
// The block records the nearest enclosing construct. // The block records the nearest enclosing construct.
@ -3541,7 +3539,7 @@ TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_If_MultiBlockLoop) {
Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null } Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null }
Construct{ IfSelection [0,6) begin_id:10 end_id:99 depth:1 parent:Function@10 } Construct{ IfSelection [0,6) begin_id:10 end_id:99 depth:1 parent:Function@10 }
Construct{ Continue [3,5) begin_id:40 end_id:89 depth:2 parent:IfSelection@10 in-c:Continue@40 } Construct{ Continue [3,5) begin_id:40 end_id:89 depth:2 parent:IfSelection@10 in-c:Continue@40 }
Construct{ Loop [1,3) begin_id:20 end_id:40 depth:2 parent:IfSelection@10 in-c-l:Loop@20 } Construct{ Loop [1,3) begin_id:20 end_id:40 depth:2 parent:IfSelection@10 in-l:Loop@20 }
})")) << constructs; })")) << constructs;
// The block records the nearest enclosing construct. // The block records the nearest enclosing construct.
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get()); EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
@ -4715,6 +4713,135 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultIsMerge) {
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak); EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
} }
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromNestedIf_Unconditional) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpSwitch %selector %99 20 %20
%20 = OpLabel
OpSelectionMerge %80 None
OpBranchConditional %cond %30 %80
%30 = OpLabel
OpBranch %99
%80 = OpLabel ; inner merge
OpBranch %99
%99 = OpLabel ; outer merge
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(30);
ASSERT_NE(bi, nullptr);
EXPECT_EQ(bi->succ_edge.count(99), 1);
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
}
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromNestedIf_Conditional) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpSwitch %selector %99 20 %20
%20 = OpLabel
OpSelectionMerge %80 None
OpBranchConditional %cond %30 %80
%30 = OpLabel
OpBranchConditional %cond2 %99 %80 ; break-if
%80 = OpLabel ; inner merge
OpBranch %99
%99 = OpLabel ; outer merge
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(30);
ASSERT_NE(bi, nullptr);
EXPECT_EQ(bi->succ_edge.count(99), 1);
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
}
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromNestedLoop_IsError) {
// It's an error because the break can only go as far as the loop.
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpSwitch %selector %99 20 %20
%20 = OpLabel
OpLoopMerge %80 %70 None
OpBranchConditional %cond %30 %80
%30 = OpLabel ; in loop construct
OpBranch %99 ; break
%70 = OpLabel
OpBranch %20
%80 = OpLabel
OpBranch %99
%99 = OpLabel ; outer merge
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
EXPECT_THAT(p->error(),
Eq("Branch from block 30 to block 99 is an invalid exit from "
"construct starting at block 20; branch bypasses block 70"));
}
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromNestedSwitch_IsError) {
// It's an error because the break can only go as far as inner switch
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpSwitch %selector %99 20 %20
%20 = OpLabel
OpSelectionMerge %80 None
OpSwitch %selector %80 30 %30
%30 = OpLabel
OpBranch %99 ; break
%80 = OpLabel
OpBranch %99
%99 = OpLabel ; outer merge
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
EXPECT_THAT(p->error(),
Eq("Branch from block 30 to block 99 is an invalid exit from "
"construct starting at block 20; branch bypasses block 80"));
}
TEST_F(SpvParserTest, ClassifyCFGEdges_LoopBreak_FromLoopBody) { TEST_F(SpvParserTest, ClassifyCFGEdges_LoopBreak_FromLoopBody) {
auto assembly = CommonTypes() + R"( auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn
@ -4811,6 +4938,125 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect) {
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak); EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
} }
TEST_F(SpvParserTest,
ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Unconditional) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpBranch %20
%20 = OpLabel
OpLoopMerge %99 %80 None
OpBranchConditional %cond %30 %99
%30 = OpLabel
OpSelectionMerge %50 None
OpBranchConditional %cond2 %40 %50
%40 = OpLabel
OpBranch %99 ; deeply nested break
%50 = OpLabel ; inner merge
OpBranch %80
%80 = OpLabel
OpBranch %20 ; backedge
%99 = 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(40);
ASSERT_NE(bi, nullptr);
EXPECT_EQ(bi->succ_edge.count(99), 1);
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
}
TEST_F(SpvParserTest,
ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Conditional) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpBranch %20
%20 = OpLabel
OpLoopMerge %99 %80 None
OpBranchConditional %cond %30 %99
%30 = OpLabel
OpSelectionMerge %50 None
OpBranchConditional %cond2 %40 %50
%40 = OpLabel
OpBranchConditional %cond3 %99 %50 ; break-if
%50 = OpLabel ; inner merge
OpBranch %80
%80 = OpLabel
OpBranch %20 ; backedge
%99 = 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(40);
ASSERT_NE(bi, nullptr);
EXPECT_EQ(bi->succ_edge.count(99), 1);
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
}
TEST_F(SpvParserTest,
ClassifyCFGEdges_LoopBreak_FromContinueConstructNestedFlow_IsError) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpBranch %20
%20 = OpLabel
OpLoopMerge %99 %40 None
OpBranchConditional %cond %30 %99
%30 = OpLabel
OpBranch %40
%40 = OpLabel ; continue construct
OpSelectionMerge %79 None
OpBranchConditional %cond2 %50 %79
%50 = OpLabel
OpBranchConditional %cond3 %99 %79 ; attempt to break to 99 should fail
%79 = OpLabel
OpBranch %80 ; inner merge
%80 = OpLabel
OpBranch %20 ; backedge
%99 = OpLabel
OpReturn
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
EXPECT_THAT(p->error(),
Eq("Invalid exit (50->99) from continue construct: 50 is not the "
"last block in the continue construct starting at 40 "
"(violates post-dominance rule)"));
}
TEST_F(SpvParserTest, ClassifyCFGEdges_LoopContinue_LoopBodyToContinue) { TEST_F(SpvParserTest, ClassifyCFGEdges_LoopContinue_LoopBodyToContinue) {
auto assembly = CommonTypes() + R"( auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn
@ -4949,7 +5195,7 @@ TEST_F(SpvParserTest,
auto* p = parser(test::Assemble(assembly)); auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100)); FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowClassifyCFGEdges(&fe)); EXPECT_TRUE(FlowClassifyCFGEdges(&fe)) << p->error();
auto* bi = fe.GetBlockInfo(40); auto* bi = fe.GetBlockInfo(40);
ASSERT_NE(bi, nullptr); ASSERT_NE(bi, nullptr);
@ -5064,7 +5310,7 @@ TEST_F(SpvParserTest,
auto* p = parser(test::Assemble(assembly)); auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100)); FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(FlowClassifyCFGEdges(&fe)); EXPECT_TRUE(FlowClassifyCFGEdges(&fe)) << p->error();
auto* bi = fe.GetBlockInfo(40); auto* bi = fe.GetBlockInfo(40);
ASSERT_NE(bi, nullptr); ASSERT_NE(bi, nullptr);