[spirv-reader] Register merges
Record header/merge cross-links, and single_block_loop attribute of BlockInfo.
Also checks that they are sane: only target blocks in the same function.
Bug: tint:3
Change-Id: I715f7ed354a556e92d58a4c9ba6f306c746c3641
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/20080
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 1f5c80b..5dd261a 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -312,6 +312,9 @@
if (!TerminatorsAreSane()) {
return false;
}
+ if (!RegisterMerges()) {
+ return false;
+ }
ComputeBlockOrderAndPositions();
@@ -359,6 +362,110 @@
return success();
}
+bool FunctionEmitter::RegisterMerges() {
+ if (failed()) {
+ return false;
+ }
+
+ const auto entry_id = function_.begin()->id();
+ for (const auto& block : function_) {
+ const auto block_id = block.id();
+ auto* block_info = GetBlockInfo(block_id);
+ if (!block_info) {
+ return Fail() << "internal error: assumed blocks were registered";
+ }
+
+ if (const auto* inst = block.GetMergeInst()) {
+ auto terminator_opcode = block.terminator()->opcode();
+ switch (inst->opcode()) {
+ case SpvOpSelectionMerge:
+ if ((terminator_opcode != SpvOpBranchConditional) &&
+ (terminator_opcode != SpvOpSwitch)) {
+ return Fail() << "Selection header " << block_id
+ << " does not end in an OpBranchConditional or "
+ "OpSwitch instruction";
+ }
+ break;
+ case SpvOpLoopMerge:
+ if ((terminator_opcode != SpvOpBranchConditional) &&
+ (terminator_opcode != SpvOpBranch)) {
+ return Fail() << "Loop header " << block_id
+ << " does not end in an OpBranch or "
+ "OpBranchConditional instruction";
+ }
+ break;
+ default:
+ break;
+ }
+
+ const uint32_t header = block.id();
+ auto* header_info = block_info;
+ const uint32_t merge = inst->GetSingleWordInOperand(0);
+ auto* merge_info = GetBlockInfo(merge);
+ if (!merge_info) {
+ return Fail() << "Structured header block " << header
+ << " declares invalid merge block " << merge;
+ }
+ if (merge == header) {
+ return Fail() << "Structured header block " << header
+ << " cannot be its own merge block";
+ }
+ if (merge_info->header_for_merge) {
+ return Fail() << "Block " << merge
+ << " declared as merge block for more than one header: "
+ << merge_info->header_for_merge << ", " << header;
+ }
+ merge_info->header_for_merge = header;
+ header_info->merge_for_header = merge;
+
+ if (inst->opcode() == SpvOpLoopMerge) {
+ if (header == entry_id) {
+ return Fail() << "Function entry block " << entry_id
+ << " cannot be a loop header";
+ }
+ const uint32_t ct = inst->GetSingleWordInOperand(1);
+ auto* ct_info = GetBlockInfo(ct);
+ if (!ct_info) {
+ return Fail() << "Structured header " << header
+ << " declares invalid continue target " << ct;
+ }
+ if (ct == merge) {
+ return Fail() << "Invalid structured header block " << header
+ << ": declares block " << ct
+ << " as both its merge block and continue target";
+ }
+ if (ct_info->header_for_continue) {
+ return Fail()
+ << "Block " << ct
+ << " declared as continue target for more than one header: "
+ << ct_info->header_for_continue << ", " << header;
+ }
+ ct_info->header_for_continue = header;
+ header_info->continue_for_header = ct;
+ }
+ }
+
+ // Check single-block loop cases.
+ bool single_block_loop = false;
+ block_info->basic_block->ForEachSuccessorLabel(
+ [&single_block_loop, block_id](const uint32_t succ) {
+ if (block_id == succ)
+ single_block_loop = true;
+ });
+ block_info->single_block_loop = single_block_loop;
+ const auto ct = block_info->continue_for_header;
+ if (single_block_loop && ct != block_id) {
+ return Fail() << "Block " << block_id
+ << " branches to itself but is not its own continue target";
+ } else if (!single_block_loop && ct == block_id) {
+ return Fail() << "Loop header block " << block_id
+ << " declares itself as its own continue target, but "
+ "does not branch to itself";
+ }
+ }
+ return success();
+}
+
void FunctionEmitter::ComputeBlockOrderAndPositions() {
block_order_ = StructuredTraverser(function_).ReverseStructuredPostOrder();
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index fcea97a..73d8cd8 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -51,6 +51,20 @@
/// The position of this block in the reverse structured post-order.
uint32_t pos = 0;
+
+ /// If this block is a header, then this is the ID of the merge block.
+ uint32_t merge_for_header = 0;
+ /// If this block is a loop header, then this is the ID of the continue
+ /// target.
+ uint32_t continue_for_header = 0;
+ /// If this block is a merge, then this is the ID of the header.
+ uint32_t header_for_merge = 0;
+ /// If this block is a continue target, then this is the ID of the loop
+ /// header.
+ uint32_t header_for_continue = 0;
+ /// Is this block a single-block loop: A loop header that declares itself
+ /// as its own continue target, and has branch to itself.
+ bool single_block_loop = false;
};
/// A FunctionEmitter emits a SPIR-V function onto a Tint AST module.
@@ -99,6 +113,13 @@
/// @returns true if terminators are sane
bool TerminatorsAreSane();
+ /// Populates merge-header cross-links and the |single_block_loop| member
+ /// of BlockInfo. Also verifies that merge instructions go to blocks in
+ /// the same function. Assumes basic blocks have been registered, and
+ /// terminators are sane.
+ /// @returns false if registration fails
+ bool RegisterMerges();
+
/// Determines the output order for the basic blocks in the function.
/// Populates |block_order_| and the |pos| block info member.
/// Assumes basic blocks have been registered.
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index d742355..6e0ae03 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -44,6 +44,8 @@
%uint = OpTypeInt 32 0
%selector = OpUndef %uint
+
+ %999 = OpConstant %uint 999
)";
}
@@ -249,7 +251,7 @@
%100 = OpFunction %void None %voidfn
%10 = OpLabel
- OpBranch %void ; definitely wrong
+ OpBranch %999 ; definitely wrong
OpFunctionEnd
)"));
@@ -257,8 +259,9 @@
FunctionEmitter fe(p, *spirv_function(100));
fe.RegisterBasicBlocks();
EXPECT_FALSE(fe.TerminatorsAreSane());
- EXPECT_THAT(p->error(), Eq("Block 10 in function 100 branches to 1 which is "
- "not a block in the function"));
+ EXPECT_THAT(p->error(),
+ Eq("Block 10 in function 100 branches to 999 which is "
+ "not a block in the function"));
}
TEST_F(SpvParserTest, TerminatorsAreSane_DisallowBlockInDifferentFunction) {
@@ -286,6 +289,596 @@
"is not a block in the function"));
}
+TEST_F(SpvParserTest, RegisterMerges_NoMerges) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_TRUE(fe.RegisterMerges());
+
+ const auto* bi = fe.GetBlockInfo(10);
+ ASSERT_NE(bi, nullptr);
+ EXPECT_EQ(bi->merge_for_header, 0u);
+ EXPECT_EQ(bi->continue_for_header, 0u);
+ EXPECT_EQ(bi->header_for_merge, 0u);
+ EXPECT_EQ(bi->header_for_continue, 0u);
+ EXPECT_FALSE(bi->single_block_loop);
+}
+
+TEST_F(SpvParserTest, RegisterMerges_GoodSelectionMerge_BranchConditional) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpSelectionMerge %99 None
+ OpBranchConditional %cond %20 %99
+
+ %20 = OpLabel
+ OpBranch %99
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_TRUE(fe.RegisterMerges());
+
+ // Header points to the merge
+ const auto* bi10 = fe.GetBlockInfo(10);
+ ASSERT_NE(bi10, nullptr);
+ EXPECT_EQ(bi10->merge_for_header, 99u);
+ EXPECT_EQ(bi10->continue_for_header, 0u);
+ EXPECT_EQ(bi10->header_for_merge, 0u);
+ EXPECT_EQ(bi10->header_for_continue, 0u);
+ EXPECT_FALSE(bi10->single_block_loop);
+
+ // Middle block is neither header nor merge
+ const auto* bi20 = fe.GetBlockInfo(20);
+ ASSERT_NE(bi20, nullptr);
+ EXPECT_EQ(bi20->merge_for_header, 0u);
+ EXPECT_EQ(bi20->continue_for_header, 0u);
+ EXPECT_EQ(bi20->header_for_merge, 0u);
+ EXPECT_EQ(bi20->header_for_continue, 0u);
+ EXPECT_FALSE(bi20->single_block_loop);
+
+ // Merge block points to the header
+ const auto* bi99 = fe.GetBlockInfo(99);
+ ASSERT_NE(bi99, nullptr);
+ EXPECT_EQ(bi99->merge_for_header, 0u);
+ EXPECT_EQ(bi99->continue_for_header, 0u);
+ EXPECT_EQ(bi99->header_for_merge, 10u);
+ EXPECT_EQ(bi99->header_for_continue, 0u);
+ EXPECT_FALSE(bi99->single_block_loop);
+}
+
+TEST_F(SpvParserTest, RegisterMerges_GoodSelectionMerge_Switch) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99 20 %20
+
+ %20 = OpLabel
+ OpBranch %99
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_TRUE(fe.RegisterMerges());
+
+ // Header points to the merge
+ const auto* bi10 = fe.GetBlockInfo(10);
+ ASSERT_NE(bi10, nullptr);
+ EXPECT_EQ(bi10->merge_for_header, 99u);
+ EXPECT_EQ(bi10->continue_for_header, 0u);
+ EXPECT_EQ(bi10->header_for_merge, 0u);
+ EXPECT_EQ(bi10->header_for_continue, 0u);
+ EXPECT_FALSE(bi10->single_block_loop);
+
+ // Middle block is neither header nor merge
+ const auto* bi20 = fe.GetBlockInfo(20);
+ ASSERT_NE(bi20, nullptr);
+ EXPECT_EQ(bi20->merge_for_header, 0u);
+ EXPECT_EQ(bi20->continue_for_header, 0u);
+ EXPECT_EQ(bi20->header_for_merge, 0u);
+ EXPECT_EQ(bi20->header_for_continue, 0u);
+ EXPECT_FALSE(bi20->single_block_loop);
+
+ // Merge block points to the header
+ const auto* bi99 = fe.GetBlockInfo(99);
+ ASSERT_NE(bi99, nullptr);
+ EXPECT_EQ(bi99->merge_for_header, 0u);
+ EXPECT_EQ(bi99->continue_for_header, 0u);
+ EXPECT_EQ(bi99->header_for_merge, 10u);
+ EXPECT_EQ(bi99->header_for_continue, 0u);
+ EXPECT_FALSE(bi99->single_block_loop);
+}
+
+TEST_F(SpvParserTest, RegisterMerges_GoodLoopMerge_SingleBlockLoop) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %99 %20 None
+ OpBranchConditional %cond %20 %99
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_TRUE(fe.RegisterMerges());
+
+ // Entry block is not special
+ const auto* bi10 = fe.GetBlockInfo(10);
+ ASSERT_NE(bi10, nullptr);
+ EXPECT_EQ(bi10->merge_for_header, 0u);
+ EXPECT_EQ(bi10->continue_for_header, 0u);
+ EXPECT_EQ(bi10->header_for_merge, 0u);
+ EXPECT_EQ(bi10->header_for_continue, 0u);
+ EXPECT_FALSE(bi10->single_block_loop);
+
+ // Single block loop is its own continue, and marked as single block loop.
+ const auto* bi20 = fe.GetBlockInfo(20);
+ ASSERT_NE(bi20, nullptr);
+ EXPECT_EQ(bi20->merge_for_header, 99u);
+ EXPECT_EQ(bi20->continue_for_header, 20u);
+ EXPECT_EQ(bi20->header_for_merge, 0u);
+ EXPECT_EQ(bi20->header_for_continue, 20u);
+ EXPECT_TRUE(bi20->single_block_loop);
+
+ // Merge block points to the header
+ const auto* bi99 = fe.GetBlockInfo(99);
+ ASSERT_NE(bi99, nullptr);
+ EXPECT_EQ(bi99->merge_for_header, 0u);
+ EXPECT_EQ(bi99->continue_for_header, 0u);
+ EXPECT_EQ(bi99->header_for_merge, 20u);
+ EXPECT_EQ(bi99->header_for_continue, 0u);
+ EXPECT_FALSE(bi99->single_block_loop);
+}
+
+TEST_F(SpvParserTest, RegisterMerges_GoodLoopMerge_MultiBlockLoop_Branch) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %99 %40 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpBranchConditional %cond %40 %99
+
+ %40 = OpLabel
+ OpBranch %20
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_TRUE(fe.RegisterMerges());
+
+ // Loop header points to continue and merge
+ const auto* bi20 = fe.GetBlockInfo(20);
+ ASSERT_NE(bi20, nullptr);
+ EXPECT_EQ(bi20->merge_for_header, 99u);
+ EXPECT_EQ(bi20->continue_for_header, 40u);
+ EXPECT_EQ(bi20->header_for_merge, 0u);
+ EXPECT_EQ(bi20->header_for_continue, 0u);
+ EXPECT_FALSE(bi20->single_block_loop);
+
+ // Continue block points to header
+ const auto* bi40 = fe.GetBlockInfo(40);
+ ASSERT_NE(bi40, nullptr);
+ EXPECT_EQ(bi40->merge_for_header, 0u);
+ EXPECT_EQ(bi40->continue_for_header, 0u);
+ EXPECT_EQ(bi40->header_for_merge, 0u);
+ EXPECT_EQ(bi40->header_for_continue, 20u);
+ EXPECT_FALSE(bi40->single_block_loop);
+
+ // Merge block points to the header
+ const auto* bi99 = fe.GetBlockInfo(99);
+ ASSERT_NE(bi99, nullptr);
+ EXPECT_EQ(bi99->merge_for_header, 0u);
+ EXPECT_EQ(bi99->continue_for_header, 0u);
+ EXPECT_EQ(bi99->header_for_merge, 20u);
+ EXPECT_EQ(bi99->header_for_continue, 0u);
+ EXPECT_FALSE(bi99->single_block_loop);
+}
+
+TEST_F(SpvParserTest,
+ RegisterMerges_GoodLoopMerge_MultiBlockLoop_BranchConditional) {
+ auto* p = parser(test::Assemble(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
+ OpBranch %20
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_TRUE(fe.RegisterMerges());
+
+ // Loop header points to continue and merge
+ const auto* bi20 = fe.GetBlockInfo(20);
+ ASSERT_NE(bi20, nullptr);
+ EXPECT_EQ(bi20->merge_for_header, 99u);
+ EXPECT_EQ(bi20->continue_for_header, 40u);
+ EXPECT_EQ(bi20->header_for_merge, 0u);
+ EXPECT_EQ(bi20->header_for_continue, 0u);
+ EXPECT_FALSE(bi20->single_block_loop);
+
+ // Continue block points to header
+ const auto* bi40 = fe.GetBlockInfo(40);
+ ASSERT_NE(bi40, nullptr);
+ EXPECT_EQ(bi40->merge_for_header, 0u);
+ EXPECT_EQ(bi40->continue_for_header, 0u);
+ EXPECT_EQ(bi40->header_for_merge, 0u);
+ EXPECT_EQ(bi40->header_for_continue, 20u);
+ EXPECT_FALSE(bi40->single_block_loop);
+
+ // Merge block points to the header
+ const auto* bi99 = fe.GetBlockInfo(99);
+ ASSERT_NE(bi99, nullptr);
+ EXPECT_EQ(bi99->merge_for_header, 0u);
+ EXPECT_EQ(bi99->continue_for_header, 0u);
+ EXPECT_EQ(bi99->header_for_merge, 20u);
+ EXPECT_EQ(bi99->header_for_continue, 0u);
+ EXPECT_FALSE(bi99->single_block_loop);
+}
+
+TEST_F(SpvParserTest, RegisterMerges_SelectionMerge_BadTerminator) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpSelectionMerge %99 None
+ OpBranch %30
+
+ %20 = OpLabel
+ OpBranch %99
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_FALSE(fe.RegisterMerges());
+ EXPECT_THAT(p->error(), Eq("Selection header 10 does not end in an "
+ "OpBranchConditional or OpSwitch instruction"));
+}
+
+TEST_F(SpvParserTest, RegisterMerges_LoopMerge_BadTerminator) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %99 %40 None
+ OpSwitch %selector %99 30 %30
+
+ %30 = OpLabel
+ OpBranch %20
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_FALSE(fe.RegisterMerges());
+ EXPECT_THAT(p->error(), Eq("Loop header 20 does not end in an OpBranch or "
+ "OpBranchConditional instruction"));
+}
+
+TEST_F(SpvParserTest, RegisterMerges_BadMergeBlock) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpSelectionMerge %void None
+ OpBranchConditional %cond %30 %99
+
+ %30 = OpLabel
+ OpBranch %99
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_FALSE(fe.RegisterMerges());
+ EXPECT_THAT(p->error(),
+ Eq("Structured header block 10 declares invalid merge block 1"));
+}
+
+TEST_F(SpvParserTest, RegisterMerges_HeaderIsItsOwnMerge) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpSelectionMerge %10 None
+ OpBranchConditional %cond %30 %99
+
+ %30 = OpLabel
+ OpBranch %99
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_FALSE(fe.RegisterMerges());
+ EXPECT_THAT(p->error(),
+ Eq("Structured header block 10 cannot be its own merge block"));
+}
+
+TEST_F(SpvParserTest, RegisterMerges_MergeReused) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpSelectionMerge %49 None
+ OpBranchConditional %cond %20 %49
+
+ %20 = OpLabel
+ OpBranch %49
+
+ %49 = OpLabel
+ OpBranch %50
+
+ %50 = OpLabel
+ OpSelectionMerge %49 None ; can't reuse merge block
+ OpBranchConditional %cond %60 %99
+
+ %60 = OpLabel
+ OpBranch %99
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_FALSE(fe.RegisterMerges());
+ EXPECT_THAT(
+ p->error(),
+ Eq("Block 49 declared as merge block for more than one header: 10, 50"));
+}
+
+TEST_F(SpvParserTest, RegisterMerges_EntryBlockIsLoopHeader) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpLoopMerge %99 %30 None
+ OpBranchConditional %cond %10 %99
+
+ %30 = OpLabel
+ OpBranch %10
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_FALSE(fe.RegisterMerges());
+ EXPECT_THAT(p->error(),
+ Eq("Function entry block 10 cannot be a loop header"));
+}
+
+TEST_F(SpvParserTest, RegisterMerges_BadContinueTarget) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %99 %999 None
+ OpBranchConditional %cond %20 %99
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_FALSE(fe.RegisterMerges());
+ EXPECT_THAT(p->error(),
+ Eq("Structured header 20 declares invalid continue target 999"));
+}
+
+TEST_F(SpvParserTest, RegisterMerges_MergeSameAsContinue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %50 %50 None
+ OpBranchConditional %cond %20 %99
+
+
+ %50 = OpLabel
+ OpBranch %20
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_FALSE(fe.RegisterMerges());
+ EXPECT_THAT(p->error(),
+ Eq("Invalid structured header block 20: declares block 50 as "
+ "both its merge block and continue target"));
+}
+
+TEST_F(SpvParserTest, RegisterMerges_ContinueReused) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %49 %40 None
+ OpBranchConditional %cond %30 %49
+
+ %30 = OpLabel
+ OpBranch %40
+
+ %40 = OpLabel
+ OpBranch %20
+
+ %49 = OpLabel
+ OpBranch %50
+
+ %50 = OpLabel
+ OpLoopMerge %99 %40 None
+ OpBranchConditional %cond %60 %99
+
+ %60 = OpLabel
+ OpBranch %70
+
+ %70 = OpLabel
+ OpBranch %50
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_FALSE(fe.RegisterMerges());
+ EXPECT_THAT(p->error(), Eq("Block 40 declared as continue target for more "
+ "than one header: 20, 50"));
+}
+
+TEST_F(SpvParserTest, RegisterMerges_SingleBlockLoop_NotItsOwnContinue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %99 %30 None
+ OpBranchConditional %cond %20 %99
+
+ %30 = OpLabel
+ OpBranch %20
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_FALSE(fe.RegisterMerges());
+ EXPECT_THAT(
+ p->error(),
+ Eq("Block 20 branches to itself but is not its own continue target"));
+}
+
+TEST_F(SpvParserTest, RegisterMerges_NotSingleBlockLoop_IsItsOwnContinue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranch %20
+
+ %20 = OpLabel
+ OpLoopMerge %99 %20 None
+ OpBranchConditional %cond %30 %99
+
+ %30 = OpLabel
+ OpBranch %20
+
+ %99 = OpLabel
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ fe.RegisterBasicBlocks();
+ EXPECT_FALSE(fe.RegisterMerges());
+ EXPECT_THAT(p->error(), Eq("Loop header block 20 declares itself as its own "
+ "continue target, but does not branch to itself"));
+}
+
TEST_F(SpvParserTest, ComputeBlockOrder_OneBlock) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn