[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:
parent
dd218b050d
commit
0131580407
|
@ -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();
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue