[spirv-reader] Emit non-header OpBranchConditional
This emits the equivalent of break-if, break-unless, continue-if,
continue-unless. But we do it via a regular if-then-else.
Adds a test matrix.
Adds all required tests except for those needing OpSwitch.
Bug: tint:3
Change-Id: I960a40aa00f95f394a92a099c8b12104010ad49f
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/22603
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 2322589..0cd954e 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -1656,6 +1656,12 @@
// Have we emitted the statements for this block?
bool emitted = false;
+
+ // When entering an if-selection or switch-selection, we will emit the WGSL
+ // construct to cause the divergent branching. But otherwise, we will
+ // emit a "normal" block terminator, which occurs at the end of this method.
+ bool has_normal_terminator = true;
+
for (auto iter = entering_constructs.rbegin();
iter != entering_constructs.rend(); ++iter) {
const Construct* construct = *iter;
@@ -1695,9 +1701,11 @@
if (!EmitIfStart(block_info)) {
return false;
}
+ has_normal_terminator = false;
break;
case Construct::kSwitchSelection:
+ has_normal_terminator = false;
return Fail() << "unhandled: switch construct";
}
}
@@ -1708,8 +1716,10 @@
return false;
}
- if (!EmitNormalTerminator(block_info)) {
- return false;
+ if (has_normal_terminator) {
+ if (!EmitNormalTerminator(block_info)) {
+ return false;
+ }
}
return success();
}
@@ -1864,10 +1874,54 @@
AddStatement(MakeBranch(block_info, *GetBlockInfo(dest_id)));
return true;
}
+ case SpvOpBranchConditional: {
+ // If both destinations are the same, then do the same as we would
+ // for an unconditional branch (OpBranch).
+ const auto true_dest = terminator.GetSingleWordInOperand(1);
+ const auto false_dest = terminator.GetSingleWordInOperand(2);
+ if (true_dest == false_dest) {
+ // This is like an uncondtional branch.
+ AddStatement(MakeBranch(block_info, *GetBlockInfo(true_dest)));
+ return true;
+ }
+
+ const EdgeKind true_kind = block_info.succ_edge.find(true_dest)->second;
+ const EdgeKind false_kind = block_info.succ_edge.find(false_dest)->second;
+ auto* const true_info = GetBlockInfo(true_dest);
+ auto* const false_info = GetBlockInfo(false_dest);
+ auto cond = MakeExpression(terminator.GetSingleWordInOperand(0)).expr;
+
+ // We have two distinct destinations. But we only get here if this
+ // is a normal terminator; in particular the source block is *not* the
+ // start of an if-selection or a switch-selection. So at most one branch
+ // is a kForward, kCaseFallThrough, or kIfBreak.
+
+ // The fallthrough case is special because WGSL requires the fallthrough
+ // statement to be last in the case clause.
+ if (true_kind == EdgeKind::kCaseFallThrough ||
+ false_kind == EdgeKind::kCaseFallThrough) {
+ return Fail() << "fallthrough is unhandled";
+ }
+
+ // At this point, at most one edge is kForward or kIfBreak.
+
+ // Emit an 'if' statement to express the *other* branch as a conditional
+ // break or continue. Either or both of these could be nullptr.
+ // (A nullptr is generated for kIfBreak, kForward, or kBack.)
+ auto true_branch = MakeBranch(block_info, *true_info);
+ auto false_branch = MakeBranch(block_info, *false_info);
+
+ AddStatement(MakeSimpleIf(std::move(cond), std::move(true_branch),
+ std::move(false_branch)));
+ return true;
+ }
+ case SpvOpSwitch:
+ // TODO(dneto)
+ break;
default:
break;
}
- // TODO(dneto): emit fallthrough, break, continue
+ // TODO(dneto): emit fallthrough
return success();
}
@@ -1903,6 +1957,31 @@
return {nullptr};
}
+std::unique_ptr<ast::Statement> FunctionEmitter::MakeSimpleIf(
+ std::unique_ptr<ast::Expression> condition,
+ std::unique_ptr<ast::Statement> then_stmt,
+ std::unique_ptr<ast::Statement> else_stmt) const {
+ if ((then_stmt == nullptr) && (else_stmt == nullptr)) {
+ return nullptr;
+ }
+ auto if_stmt = std::make_unique<ast::IfStatement>();
+ if_stmt->set_condition(std::move(condition));
+ if (then_stmt != nullptr) {
+ ast::StatementList stmts;
+ stmts.emplace_back(std::move(then_stmt));
+ if_stmt->set_body(std::move(stmts));
+ }
+ if (else_stmt != nullptr) {
+ ast::StatementList stmts;
+ stmts.emplace_back(std::move(else_stmt));
+ ast::ElseStatementList else_stmts;
+ else_stmts.emplace_back(
+ std::make_unique<ast::ElseStatement>(nullptr, std::move(stmts)));
+ if_stmt->set_else_statements(std::move(else_stmts));
+ }
+ return if_stmt;
+}
+
bool FunctionEmitter::EmitStatementsInBasicBlock(const BlockInfo& block_info,
bool* already_emitted) {
if (*already_emitted) {
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index b98eedf..246038f 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -341,12 +341,25 @@
/// Returns a new statement to represent the given branch representing a
/// "normal" terminator, as in the sense of EmitNormalTerminator. If no
- /// WGSL statement is required, the statement will nullptr.
+ /// WGSL statement is required, the statement will be nullptr.
/// @param src_info the source block
/// @param dest_info the destination block
+ /// @returns the new statement, or a null statement
std::unique_ptr<ast::Statement> MakeBranch(const BlockInfo& src_info,
const BlockInfo& dest_info) const;
+ /// Returns a new if statement with the given statements as the then-clause
+ /// and the else-clause. Either or both clauses might be nullptr. If both
+ /// are nullptr, then don't make a new statement and instead return nullptr.
+ /// @param condition the branching condition
+ /// @param then_stmt the statement for the then clause of the if, or nullptr
+ /// @param else_stmt the statement for the else clause of the if, or nullptr
+ /// @returns the new statement, or nullptr
+ std::unique_ptr<ast::Statement> MakeSimpleIf(
+ std::unique_ptr<ast::Expression> condition,
+ std::unique_ptr<ast::Statement> then_stmt,
+ std::unique_ptr<ast::Statement> else_stmt) const;
+
/// Emits a normal instruction: not a terminator, label, or variable
/// declaration.
/// @param inst the instruction
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index c2887dc..52b42f1 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -71,6 +71,7 @@
%uint_4 = OpConstant %uint 4
%uint_5 = OpConstant %uint 5
%uint_6 = OpConstant %uint 6
+ %uint_7 = OpConstant %uint 7
%ptr_Private_uint = OpTypePointer Private %uint
%var = OpVariable %ptr_Private_uint Private
@@ -4540,7 +4541,35 @@
}
TEST_F(SpvParserTest,
- ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop) {
+ ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch) {
+ auto assembly = CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel ; single block loop
+ OpLoopMerge %99 %20 None
+ OpBranchConditional %cond %99 %20
+
+ %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(20);
+ ASSERT_NE(bi, nullptr);
+ EXPECT_EQ(bi->succ_edge.count(99), 1u);
+ EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+ EXPECT_EQ(bi->succ_edge.count(20), 1u);
+ EXPECT_EQ(bi->succ_edge[20], EdgeKind::kBack);
+}
+
+TEST_F(SpvParserTest,
+ ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_FalseBranch) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -4563,6 +4592,8 @@
ASSERT_NE(bi, nullptr);
EXPECT_EQ(bi->succ_edge.count(99), 1u);
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
+ EXPECT_EQ(bi->succ_edge.count(20), 1u);
+ EXPECT_EQ(bi->succ_edge[20], EdgeKind::kBack);
}
TEST_F(SpvParserTest,
@@ -4709,6 +4740,46 @@
"construct starting at block 10; branch bypasses merge block 50"));
}
+TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_EscapeSwitchCase_IsError) {
+ // Code generation assumes that you can't have kCaseFallThrough and kIfBreak
+ // from the same OpBranchConditional.
+ // This checks one direction of that, where the IfBreak is shown it can't
+ // escape a switch case.
+ auto assembly = CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpSelectionMerge %99 None ; Set up if-break to %99
+ OpBranchConditional %cond %20 %99
+
+ %20 = OpLabel
+ OpSelectionMerge %80 None ; switch-selection
+ OpSwitch %selector %80 30 %30 40 %40
+
+ %30 = OpLabel ; first case
+ ; branch to %99 would be an if-break, but it bypasess the switch merge
+ ; Also has case fall-through
+ OpBranchConditional %cond2 %99 %40
+
+ %40 = OpLabel ; second case
+ OpBranch %80
+
+ %80 = OpLabel ; switch-selection's merge
+ OpBranch %99
+
+ %99 = OpLabel ; if-selection's 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 merge block 80"));
+}
+
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromSwitchCaseDirect) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -5557,6 +5628,56 @@
EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
}
+TEST_F(SpvParserTest,
+ ClassifyCFGEdges_LoopContinue_FromNestedLoopHeader_IsError) {
+ // Inner loop header tries to do continue to outer loop continue target.
+ // This is disallowed by the rule:
+ // "a continue block is valid only for the innermost loop it is nested
+ // inside of"
+ auto assembly = CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranchConditional %cond %30 %99
+
+ %30 = OpLabel ; inner loop.
+ OpStore %var %uint_1
+ OpLoopMerge %59 %50 None
+ OpBranchConditional %cond %59 %80 ; break and outer continue
+
+ %50 = OpLabel
+ OpStore %var %uint_2
+ OpBranch %30 ; inner backedge
+
+ %59 = OpLabel ; inner merge
+ OpStore %var %uint_3
+ OpBranch %80
+
+ %80 = OpLabel ; outer continue
+ OpStore %var %uint_4
+ OpBranch %20 ; outer backedge
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )";
+ 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 80 is an invalid exit from construct "
+ "starting at block 30; branch bypasses merge block 59"));
+}
+
TEST_F(SpvParserTest, ClassifyCFGEdges_Fallthrough_CaseTailToCase) {
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -5651,6 +5772,247 @@
}
TEST_F(SpvParserTest,
+ ClassifyCFGEdges_Fallthrough_BranchConditionalWith_IfBreak_IsError) {
+ // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+ // with kIfBreak.
+ auto assembly = CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpSelectionMerge %99 None ; Set up if-break to %99
+ OpBranchConditional %cond %20 %99
+
+ %20 = OpLabel
+ OpSelectionMerge %80 None ; switch-selection
+ OpSwitch %selector %80 30 %30 40 %40
+
+ %30 = OpLabel ; first case
+ ; branch to %99 would be an if-break, but it bypasess the switch merge
+ ; Also has case fall-through
+ OpBranchConditional %cond2 %99 %40
+
+ %40 = OpLabel ; second case
+ OpBranch %80
+
+ %80 = OpLabel ; switch-selection's merge
+ OpBranch %99
+
+ %99 = OpLabel ; if-selection's 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 merge block 80"));
+}
+
+TEST_F(SpvParserTest,
+ ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Forward_IsError) {
+ // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+ // with kForward.
+ auto assembly = CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpSelectionMerge %99 None ; switch-selection
+ OpSwitch %selector %99 20 %20 30 %30
+
+ ; Try to make branch to 35 a kForward branch
+ %20 = OpLabel ; first case
+ OpBranchConditional %cond2 %25 %30
+
+ %25 = OpLabel
+ OpBranch %99
+
+ %30 = OpLabel ; second case
+ OpBranch %99
+
+ %99 = OpLabel ; if-selection's 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("Control flow diverges at block 20 (to 25, 30) but it is not "
+ "a structured header (it has no merge instruction)"));
+}
+
+TEST_F(
+ SpvParserTest,
+ ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnOutside_IsError) {
+ // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+ // with kBack.
+ //
+ // This test has the loop on the outside. The backedge coming from a case
+ // clause means the switch is inside the continue construct, and the nesting
+ // of the switch's merge means the backedge is coming from a block that is not
+ // at the end of the continue construct.
+ auto assembly = CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %99 %30 None
+ OpBranch %30
+
+ %30 = OpLabel ; continue target and
+ OpSelectionMerge %80 None ; switch-selection
+ OpSwitch %selector %80 40 %40 50 %50
+
+ ; try to make a back edge with a fallthrough
+ %40 = OpLabel ; first case
+ OpBranchConditional %cond2 %20 %50
+
+ %50 = OpLabel ; second case
+ OpBranch %80
+
+ %80 = OpLabel ; switch merge
+ OpBranch %20 ; also backedge
+
+ %99 = OpLabel ; loop 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("Invalid exit (40->20) from continue construct: 40 is not the "
+ "last block in the continue construct starting at 30 "
+ "(violates post-dominance rule)"));
+}
+
+TEST_F(
+ SpvParserTest,
+ FindSwitchCaseSelectionHeaders_Fallthrough_BranchConditionalWith_Back_LoopOnInside_FallthroughIsMerge_IsError) {
+ // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+ // with kBack.
+ //
+ // This test has the loop on the inside. The merge block is also the
+ // fallthrough target.
+ auto assembly = CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel ; continue target and
+ OpSelectionMerge %99 None ; switch-selection
+ OpSwitch %selector %99 20 %20 50 %50
+
+ %20 = OpLabel ; first case, and loop header
+ OpLoopMerge %50 %40 None
+ OpBranch %40
+
+ ; try to make a back edge with a fallthrough
+ %40 = OpLabel
+ OpBranchConditional %cond2 %20 %50
+
+ %50 = OpLabel ; second case. also the loop merge ; header must dominate its merge block !
+ OpBranch %99
+
+ %99 = OpLabel ; switch merge
+ OpReturn
+)";
+ auto* p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
+ EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 50, 99));
+ EXPECT_THAT(p->error(),
+ Eq("Block 50 is a case block for switch-selection header 10 and "
+ "also the merge block for 20 (violates dominance rule)"));
+}
+
+TEST_F(
+ SpvParserTest,
+ ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnInside_FallthroughIsNotMerge_IsError) {
+ // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+ // with kBack.
+ //
+ // This test has the loop on the inside. The merge block is not the merge
+ // target But the block order gets messed up because of the weird
+ // connectivity.
+ auto assembly = CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel ; continue target and
+ OpSelectionMerge %99 None ; switch-selection
+ OpSwitch %selector %99 20 %20 50 %50
+
+ %20 = OpLabel ; first case, and loop header
+ OpLoopMerge %45 %40 None ; move the merge to an unreachable block
+ OpBranch %40
+
+ ; try to make a back edge with a fallthrough
+ %40 = OpLabel
+ OpBranchConditional %cond2 %20 %50
+
+ %45 = OpLabel ; merge for the loop
+ OpUnreachable
+
+ %50 = OpLabel ; second case. target of fallthrough
+ OpBranch %99
+
+ %99 = OpLabel ; switch 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 10 to 50 bypasses continue target 40 "
+ "(dominance rule violated)"));
+}
+
+TEST_F(
+ SpvParserTest,
+ ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnInside_NestedMerge_IsError) {
+ // Code generation assumes OpBranchConditional can't have kCaseFallThrough
+ // with kBack.
+ //
+ // This test has the loop on the inside. The fallthrough is an invalid exit
+ // from the loop. However, the block order gets all messed up because going
+ // from 40 to 50 ends up pulling in 99
+ auto assembly = CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel ; continue target and
+ OpSelectionMerge %99 None ; switch-selection
+ OpSwitch %selector %99 20 %20 50 %50
+
+ %20 = OpLabel ; first case, and loop header
+ OpLoopMerge %49 %40 None
+ OpBranch %40
+
+ ; try to make a back edge with a fallthrough
+ %40 = OpLabel
+ OpBranchConditional %cond2 %20 %50
+
+ %49 = OpLabel ; loop merge
+ OpBranch %99
+
+ %50 = OpLabel ; second case
+ OpBranch %99
+
+ %99 = OpLabel ; switch 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(fe.block_order(), ElementsAre(10, 20, 40, 50, 49, 99));
+ EXPECT_THAT(p->error(), Eq("Branch from 10 to 50 bypasses continue target 40 "
+ "(dominance rule violated)"));
+}
+
+TEST_F(SpvParserTest,
ClassifyCFGEdges_Fallthrough_CaseNonTailToCase_TrueBranch) {
// This is an unusual one, and is an error. Structurally it looks like this:
// switch (val) {
@@ -6866,45 +7228,123 @@
%100 = OpFunction %void None %voidfn
%10 = OpLabel
+ OpStore %var %uint_1
OpSelectionMerge %99 None
- OpBranchConditional %cond %20 %99
+ OpBranchConditional %cond %20 %50
%20 = OpLabel
- OpBranchConditional %cond2 %99 %80 ; break with forward edge
+ OpStore %var %uint_2
+ OpBranchConditional %cond2 %99 %30 ; kIfBreak with kForward
- %80 = OpLabel ; still in then clause
+ %30 = OpLabel ; still in then clause
+ OpStore %var %uint_3
+ OpBranch %99
+
+ %50 = OpLabel ; else clause
+ OpStore %var %uint_4
OpBranch %99
%99 = OpLabel
+ OpStore %var %uint_5
OpReturn
OpFunctionEnd
)";
+ auto* p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(something
+Return{}
+)")) << ToString(fe.ast_body());
}
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
+ // at block 80
auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
+ OpStore %var %uint_1
OpSelectionMerge %99 None
- OpBranchConditional %cond %20 %30
+ OpBranchConditional %cond %20 %50
%20 = OpLabel
+ OpStore %var %uint_2
OpBranch %99
- %30 = OpLabel ; else clause
- OpBranchConditional %cond2 %99 %80 ; break with forward edge
+ %50 = OpLabel ; else clause
+ OpStore %var %uint_3
+ OpBranchConditional %cond2 %99 %80 ; kIfBreak with kForward
%80 = OpLabel ; still in then clause
+ OpStore %var %uint_4
OpBranch %99
%99 = OpLabel
+ OpStore %var %uint_5
OpReturn
OpFunctionEnd
)";
+ auto* p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(something
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(
+ SpvParserTest,
+ DISABLED_Codegen_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge) {
+ // This is a combination of the previous two, but also adding a premrge and
+ //
+ auto assembly = CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpBranchConditional %cond %20 %50
+
+ %20 = OpLabel ; then
+ OpStore %var %uint_2
+ OpBranchConditional %cond2 %21 %99 ; kForward and kIfBreak
+
+ %21 = OpLabel ; still in then clause
+ OpStore %var %uint_2
+ OpBranch %80 ; to premerge
+
+ %50 = OpLabel ; else clause
+ OpStore %var %uint_3
+ OpBranchConditional %cond2 %99 %51 ; kIfBreak with kForward
+
+ %51 = OpLabel ; still in else clause
+ OpStore %var %uint_4
+ OpBranch %80 ; to premerge
+
+ %80 = OpLabel ; premerge
+ OpStore %var %uint_5
+ OpBranchConditional %cond3 %81 %99
+
+ %81 = OpLabel ; premerge
+ OpStore %var %uint_6
+ OpBranch %99
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+ OpFunctionEnd
+)";
+ auto* p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error() << assembly;
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(something
+Return{}
+)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest, BlockIsContinueForMoreThanOneHeader) {
@@ -7434,7 +7874,6 @@
}
TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_TrueBackedge) {
- // TODO(dneto): emit conditional break
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -7466,17 +7905,28 @@
Identifier{var}
ScalarConstructor{1}
}
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Break{}
+ }
+ }
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
Return{}
-)"));
+)")) << ToString(fe.ast_body());
}
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_SingleBlock_FalseBackedge) {
- // TODO(dneto): emit conditional break
+TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_FalseBackedge) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -7508,13 +7958,21 @@
Identifier{var}
ScalarConstructor{1}
}
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Break{}
+ }
+ }
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
Return{}
-)"));
+)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_BothBackedge) {
@@ -7555,7 +8013,7 @@
ScalarConstructor{999}
}
Return{}
-)"));
+)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_UnconditionalBackege) {
@@ -7812,20 +8270,189 @@
)")) << ToString(fe.ast_body());
}
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_Never) {
+TEST_F(SpvParserTest, EmitBody_Loop_Never) {
// Test case where both branches exit. e.g both go to merge.
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranchConditional %cond %99 %99
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_2
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_3
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Break{}
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+}
+Return{}
+)")) << ToString(fe.ast_body());
}
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_HeaderBreakAndContinue) {
+TEST_F(SpvParserTest, EmitBody_Loop_HeaderBreakAndContinue) {
// Header block branches to merge, and to an outer continue.
+ // This is disallowed by the rule:
+ // "a continue block is valid only for the innermost loop it is nested
+ // inside of"
+ // See test ClassifyCFGEdges_LoopContinue_FromNestedLoopHeader_IsError
}
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_TrueToBody) {
- // TODO(dneto): Needs break-unless
+TEST_F(SpvParserTest, EmitBody_Loop_TrueToBody_FalseBreaks) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranchConditional %cond %30 %99
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_3
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_4
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Break{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+}
+Return{}
+)")) << ToString(fe.ast_body());
}
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_FalseToBody) {
- // TODO(dneto): Needs break-if
+TEST_F(SpvParserTest, EmitBody_Loop_FalseToBody_TrueBreaks) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranchConditional %cond %30 %99
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_3
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_4
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Break{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+}
+Return{}
+)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest, EmitBody_Loop_NestedIfContinue) {
@@ -7941,8 +8568,240 @@
)")) << ToString(fe.ast_body());
}
-TEST_F(SpvParserTest, DISABLED_EmitBody_Loop_BodyConditionallyBreaks) {
- // TODO(dneto): Needs "break" support
+TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromTrue) {
+ // The else-branch has a continue but it's skipped because it's from a
+ // block that immediately precedes the continue construct.
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_1
+ OpBranchConditional %cond %99 %80
+
+ %80 = OpLabel
+ OpStore %var %uint_2
+ OpBranch %20 ; backedge
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Break{}
+ }
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromFalse) {
+ // The else-branch has a continue but it's skipped because it's from a
+ // block that immediately precedes the continue construct.
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_1
+ OpBranchConditional %cond %80 %99
+
+ %80 = OpLabel
+ OpStore %var %uint_2
+ OpBranch %20 ; backedge
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Break{}
+ }
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_1
+ OpBranchConditional %cond %99 %70
+
+ %70 = OpLabel
+ OpStore %var %uint_3
+ OpBranch %80
+
+ %80 = OpLabel
+ OpStore %var %uint_2
+ OpBranch %20 ; backedge
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Break{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromFalse_Early) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_1
+ OpBranchConditional %cond %70 %99
+
+ %70 = OpLabel
+ OpStore %var %uint_3
+ OpBranch %80
+
+ %80 = OpLabel
+ OpStore %var %uint_2
+ OpBranch %20 ; backedge
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Break{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ }
+}
+Return{}
+)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest, EmitBody_Return_TopLevel) {
@@ -8435,10 +9294,6 @@
// TODO(dneto): support switch first.
}
-TEST_F(SpvParserTest, EmitBody_Branch_LoopBreak_SingleBlockLoop) {
- // This case is impossible. The loop must have a backedge,
-}
-
TEST_F(SpvParserTest, EmitBody_Branch_LoopBreak_MultiBlockLoop_FromBody) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -8761,7 +9616,7 @@
)")) << ToString(fe.ast_body());
}
-TEST_F(SpvParserTest, DISABLED_EmitBody_Branch_CaseFallthrough) {
+TEST_F(SpvParserTest, DISABLED_EmitBody_Branch_Fallthrough) {
// TODO(dneto): support switch first.
}
@@ -8794,27 +9649,1485 @@
)")) << ToString(fe.ast_body());
}
-// For normal terminator that is OpBranchConditional, the kBack, kForward,
-// kIfBreak are all invalid proven in earlier tests.
-TEST_F(SpvParserTest, DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Same) {
- // Can only be to the same target.
- // TODO(dneto): needs switch support
+// Test matrix for normal OpBranchConditional:
+//
+// kBack with:
+// kBack : TESTED dup general case
+// kSwitchBreak: invalid (invalid escape, or invalid backedge)
+// kLoopBreak: TESTED in single- and multi block loop configurations
+// kLoopContinue: invalid
+// If single block loop, then the continue is backward
+// If continue is forward, then it's a continue from a
+// continue which is also invalid.
+// kIfBreak: invalid: loop and if must have distinct merge blocks
+// kCaseFallThrough: invalid: loop header must dominate its merge
+// kForward: impossible; would be a loop break
+//
+// kSwitchBreak with:
+// kBack : symmetry
+// kSwitchBreak: dup general case
+// kLoopBreak: invalid; only one kind of break allowed
+// kLoopContinue: TODO(dneto)
+// kIfBreak: invalid: switch and if must have distinct merge blocks
+// kCaseFallThrough: TODO(dneto)
+// kForward: TODO(dneto)
+//
+// kLoopBreak with:
+// kBack : symmetry
+// kSwitchBreak: symmetry
+// kLoopBreak: dup general case
+// kLoopContinue: TESTED
+// kIfBreak: invalid: switch and if must have distinct merge blocks
+// kCaseFallThrough: TODO(dneto)
+// kForward: TESTED
+//
+// kLoopContinue with:
+// kBack : symmetry
+// kSwitchBreak: symmetry
+// kLoopBreak: symmetry
+// kLoopContinue: dup general case
+// kIfBreak: TODO(dneto)
+// kCaseFallThrough: TODO(dneto)
+// kForward: TESTED
+//
+// kIfBreak with:
+// kBack : symmetry
+// kSwitchBreak: symmetry
+// kLoopBreak: symmetry
+// kLoopContinue: symmetry
+// kIfBreak: dup general case
+// kCaseFallThrough: invalid; violates nesting or unique merges
+// kForward: invalid: needs a merge instruction
+//
+// kCaseFallThrough with:
+// kBack : symmetry
+// kSwitchBreak: symmetry
+// kLoopBreak: symmetry
+// kLoopContinue: symmetry
+// kIfBreak: symmetry
+// kCaseFallThrough: dup general case
+// kForward: invalid (tested)
+//
+// kForward with:
+// kBack : symmetry
+// kSwitchBreak: symmetry
+// kLoopBreak: symmetry
+// kLoopContinue: symmetry
+// kIfBreak: symmetry
+// kCaseFallThrough: symmetry
+// kForward: dup general case
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Back_SingleBlock_Back) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %20 None
+ OpBranchConditional %cond %20 %20
+
+ %99 = OpLabel ; dead
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
TEST_F(SpvParserTest,
- DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Different_IsError) {
- // TODO(dneto): needs switch support
+ EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %20 None
+ OpBranchConditional %cond %99 %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
}
-TEST_F(SpvParserTest, DISABLED_EmitBody_BranchConditional_Fallthrough_LoopBreak) {
- // TODO(dneto): needs switch support
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Break{}
+ }
+ }
}
-TEST_F(SpvParserTest, DISABLED_EmitBody_BranchConditional_Fallthrough_SwitchBreak) {
- // TODO(dneto): needs switch support
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
}
-TEST_F(SpvParserTest, DISABLED_EmitBody_BranchConditional_Fallthrough_LoopContinue) {
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+ EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnFalse) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %20 None
+ OpBranchConditional %cond %20 %99
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Break{}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+ EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %80
+
+ %80 = OpLabel
+ OpBranchConditional %cond %99 %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ continuing {
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Break{}
+ }
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+ EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnFalse) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %80
+
+ %80 = OpLabel
+ OpBranchConditional %cond %20 %99
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ continuing {
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Break{}
+ }
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue) {
// TODO(dneto): needs switch support
}
-// TODO(dneto): test normal OpBranchConditional with other than Fallthrough
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse) {
+ // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_SwitchBreak_Forward_OnTrue) {
+ // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_SwitchBreak_Forward_OnFalse) {
+ // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue) {
+ // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse) {
+ // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest,
+ EmitBody_BranchConditional_LoopBreak_SingleBlock_LoopBreak) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranchConditional %cond %99 %99
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_4
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Break{}
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+ EmitBody_BranchConditional_LoopBreak_MultiBlock_LoopBreak) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+ OpBranchConditional %cond %99 %99
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_4
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ Break{}
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Continue_OnTrue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %25
+
+ ; Need this extra selection to make another block between
+ ; %30 and the continue target, so we actually induce a Continue
+ ; statement to exist.
+ %25 = OpLabel
+ OpSelectionMerge %40 None
+ OpBranchConditional %cond2 %30 %40
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+; break; continue on true
+ OpBranchConditional %cond %80 %99
+
+ %40 = OpLabel
+ OpStore %var %uint_3
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_4
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Continue{}
+ }
+ }
+ Else{
+ {
+ Break{}
+ }
+ }
+ }
+ }
+ Else{
+ {
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Continue_OnFalse) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %25
+
+ ; Need this extra selection to make another block between
+ ; %30 and the continue target, so we actually induce a Continue
+ ; statement to exist.
+ %25 = OpLabel
+ OpSelectionMerge %40 None
+ OpBranchConditional %cond2 %30 %40
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+; break; continue on false
+ OpBranchConditional %cond %99 %80
+
+ %40 = OpLabel
+ OpStore %var %uint_3
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_4
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Break{}
+ }
+ }
+ Else{
+ {
+ Continue{}
+ }
+ }
+ }
+ }
+ Else{
+ {
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_LoopBreak_Fallthrough_OnTrue) {
+ // TODO(dneto): needs switch support
+}
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_LoopBreak_Fallthrough_OnFalse) {
+ // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Forward_OnTrue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+; break; forward on true
+ OpBranchConditional %cond %40 %99
+
+ %40 = OpLabel
+ OpStore %var %uint_3
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_4
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Break{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Forward_OnFalse) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+; break; forward on false
+ OpBranchConditional %cond %99 %40
+
+ %40 = OpLabel
+ OpStore %var %uint_3
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_4
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Break{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Continue_FromHeader) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranchConditional %cond %80 %80 ; to continue
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_4
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+ EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+ OpBranchConditional %cond %80 %80 ; to continue
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_4
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+ EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional) {
+ // Create an intervening block so we actually require a "continue" statement
+ // instead of just an adjacent fallthrough to the continue target.
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+ OpSelectionMerge %50 None
+ OpBranchConditional %cond2 %40 %50
+
+ %40 = OpLabel
+ OpStore %var %uint_3
+ OpBranchConditional %cond3 %80 %80 ; to continue
+
+ %50 = OpLabel ; merge for selection
+ OpStore %var %uint_4
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_5
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_6
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ Continue{}
+ }
+ }
+ Else{
+ {
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{6}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_IfBreak_OnTrue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+ OpSelectionMerge %50 None
+ OpBranchConditional %cond2 %40 %50
+
+ %40 = OpLabel
+ OpStore %var %uint_3
+ ; true to if's merge; false to continue
+ OpBranchConditional %cond3 %50 %80
+
+ %50 = OpLabel ; merge for selection
+ OpStore %var %uint_4
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_5
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_6
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Continue{}
+ }
+ }
+ }
+ }
+ Else{
+ {
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{6}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_IfBreak_OnFalse) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+ OpSelectionMerge %50 None
+ OpBranchConditional %cond2 %40 %50
+
+ %40 = OpLabel
+ OpStore %var %uint_3
+ ; false to if's merge; true to continue
+ OpBranchConditional %cond3 %80 %50
+
+ %50 = OpLabel ; merge for selection
+ OpStore %var %uint_4
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_5
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_6
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Continue{}
+ }
+ }
+ }
+ }
+ Else{
+ {
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{6}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue) {
+ // TODO(dneto): needs switch support
+}
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse) {
+ // TODO(dneto): needs switch support
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Forward_OnTrue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+; continue; forward on true
+ OpBranchConditional %cond %40 %80
+
+ %40 = OpLabel
+ OpStore %var %uint_3
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_4
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Continue{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Forward_OnFalse) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+; continue; forward on true
+ OpBranchConditional %cond %80 %40
+
+ %40 = OpLabel
+ OpStore %var %uint_3
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_4
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Continue{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_IfBreak_IfBreak_Same) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpSelectionMerge %99 None
+ OpBranchConditional %cond %99 %99
+
+ %20 = OpLabel ; dead
+ OpStore %var %uint_1
+ OpBranch %99
+
+ %99 = OpLabel
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{0}
+}
+If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+}
+Else{
+ {
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest,
+ EmitBody_BranchConditional_IfBreak_IfBreak_DifferentIsError) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_0
+ OpSelectionMerge %99 None
+ OpBranchConditional %cond %20 %99
+
+ %20 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %89 None
+ OpBranchConditional %cond %30 %89
+
+ %30 = OpLabel
+ OpStore %var %uint_2
+ OpBranchConditional %cond %89 %99 ; invalid divergence
+
+ %89 = OpLabel ; inner if-merge
+ OpBranch %99
+
+ %99 = OpLabel ; outer if-merge
+ OpStore %var %uint_5
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ 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 merge block 89"));
+}
+
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Same) {
+ // Can only be to the same target.
+ // TODO(dneto): needs switch support
+}
+TEST_F(
+ SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Different_IsError) {
+ // TODO(dneto): needs switch support
+}
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_Forward_Forward_Same) {
+ // TODO(dneto): needs switch support
+}
+TEST_F(SpvParserTest,
+ DISABLED_EmitBody_BranchConditional_Forward_Forward_Different_IsError) {
+ // TODO(dneto): needs switch support
+}
TEST_F(SpvParserTest,
DISABLED_Switch_NotAsSelectionHeader_NonDefaultBranchesAreContinue) {