[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>
This commit is contained in:
David Neto 2020-04-23 20:15:33 +00:00
parent dd218b050d
commit 0131580407
3 changed files with 724 additions and 3 deletions

View File

@ -312,6 +312,9 @@ bool FunctionEmitter::EmitBody() {
if (!TerminatorsAreSane()) {
return false;
}
if (!RegisterMerges()) {
return false;
}
ComputeBlockOrderAndPositions();
@ -359,6 +362,110 @@ bool FunctionEmitter::TerminatorsAreSane() {
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();

View File

@ -51,6 +51,20 @@ struct BlockInfo {
/// 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 @@ class FunctionEmitter {
/// @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.

View File

@ -44,6 +44,8 @@ std::string CommonTypes() {
%uint = OpTypeInt 32 0
%selector = OpUndef %uint
%999 = OpConstant %uint 999
)";
}
@ -249,7 +251,7 @@ TEST_F(SpvParserTest, TerminatorsAreSane_DisallowNonBlock) {
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpBranch %void ; definitely wrong
OpBranch %999 ; definitely wrong
OpFunctionEnd
)"));
@ -257,8 +259,9 @@ TEST_F(SpvParserTest, TerminatorsAreSane_DisallowNonBlock) {
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 @@ TEST_F(SpvParserTest, TerminatorsAreSane_DisallowBlockInDifferentFunction) {
"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