[spirv-reader] Find switch case headers
Bug: tint:3 Change-Id: I66785fd6cbbe1432a4eda3f3258e4b9b0457f303 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/20422 Reviewed-by: dan sinclair <dsinclair@google.com>
This commit is contained in:
parent
3051bfd6b5
commit
8ff8c07ac3
|
@ -440,6 +440,9 @@ bool FunctionEmitter::EmitBody() {
|
|||
if (!LabelControlFlowConstructs()) {
|
||||
return false;
|
||||
}
|
||||
if (!FindSwitchCaseHeaders()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EmitFunctionVariables()) {
|
||||
return false;
|
||||
|
@ -806,6 +809,111 @@ bool FunctionEmitter::LabelControlFlowConstructs() {
|
|||
return success();
|
||||
}
|
||||
|
||||
bool FunctionEmitter::FindSwitchCaseHeaders() {
|
||||
if (failed()) {
|
||||
return false;
|
||||
}
|
||||
for (auto& construct : constructs_) {
|
||||
if (construct->kind != Construct::kSelection) {
|
||||
continue;
|
||||
}
|
||||
const auto* branch =
|
||||
GetBlockInfo(construct->begin_id)->basic_block->terminator();
|
||||
if (branch->opcode() != SpvOpSwitch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mark the default block
|
||||
const auto default_id = branch->GetSingleWordInOperand(1);
|
||||
auto* default_block = GetBlockInfo(default_id);
|
||||
// A default target can't be a backedge.
|
||||
if (construct->begin_pos >= default_block->pos) {
|
||||
// An OpSwitch must dominate its cases. Also, it can't be a self-loop
|
||||
// as that would be a backedge, and backedges can only target a loop,
|
||||
// and loops use an OpLoopMerge instruction, which can't preceded an
|
||||
// OpSwitch.
|
||||
return Fail() << "Switch branch from block " << construct->begin_id
|
||||
<< " to default target block " << default_id
|
||||
<< " can't be a back-edge";
|
||||
}
|
||||
// A default target can be the merge block, but can't go past it.
|
||||
if (construct->end_pos < default_block->pos) {
|
||||
return Fail() << "Switch branch from block " << construct->begin_id
|
||||
<< " to default block " << default_id
|
||||
<< " escapes the selection construct";
|
||||
}
|
||||
if (default_block->default_head_for) {
|
||||
// An OpSwitch must dominate its cases, including the default target.
|
||||
return Fail() << "Block " << default_id
|
||||
<< " is declared as the default target for two OpSwitch "
|
||||
"instructions, at blocks "
|
||||
<< default_block->default_head_for->begin_id << " and "
|
||||
<< construct->begin_id;
|
||||
}
|
||||
|
||||
default_block->default_head_for = construct.get();
|
||||
default_block->default_is_merge = default_block->pos == construct->end_pos;
|
||||
|
||||
// Map a case target to the list of values selecting that case.
|
||||
std::unordered_map<uint32_t, std::vector<uint64_t>> block_to_values;
|
||||
std::vector<uint32_t> case_targets;
|
||||
std::unordered_set<uint64_t> case_values;
|
||||
|
||||
// Process case targets.
|
||||
for (uint32_t iarg = 2; iarg + 1 < branch->NumInOperands(); iarg += 2) {
|
||||
const auto o = branch->GetInOperand(iarg);
|
||||
const auto value = branch->GetInOperand(iarg).AsLiteralUint64();
|
||||
const auto case_target_id = branch->GetSingleWordInOperand(iarg + 1);
|
||||
|
||||
if (case_values.count(value)) {
|
||||
return Fail() << "Duplicate case value " << value
|
||||
<< " in OpSwitch in block " << construct->begin_id;
|
||||
}
|
||||
case_values.insert(value);
|
||||
if (block_to_values.count(case_target_id) == 0) {
|
||||
case_targets.push_back(case_target_id);
|
||||
}
|
||||
block_to_values[case_target_id].push_back(value);
|
||||
}
|
||||
|
||||
for (uint32_t case_target_id : case_targets) {
|
||||
auto* case_block = GetBlockInfo(case_target_id);
|
||||
|
||||
case_block->case_values = std::make_unique<std::vector<uint64_t>>(std::move(block_to_values[case_target_id]));
|
||||
|
||||
// A case target can't be a back-edge.
|
||||
if (construct->begin_pos >= case_block->pos) {
|
||||
// An OpSwitch must dominate its cases. Also, it can't be a self-loop
|
||||
// as that would be a backedge, and backedges can only target a loop,
|
||||
// and loops use an OpLoopMerge instruction, which can't preceded an
|
||||
// OpSwitch.
|
||||
return Fail() << "Switch branch from block " << construct->begin_id
|
||||
<< " to case target block " << case_target_id
|
||||
<< " can't be a back-edge";
|
||||
}
|
||||
// A case target can be the merge block, but can't go past it.
|
||||
if (construct->end_pos < case_block->pos) {
|
||||
return Fail() << "Switch branch from block " << construct->begin_id
|
||||
<< " to case target block " << case_target_id
|
||||
<< " escapes the selection construct";
|
||||
}
|
||||
|
||||
// Mark the target as a case target.
|
||||
if (case_block->case_head_for) {
|
||||
// An OpSwitch must dominate its cases.
|
||||
return Fail()
|
||||
<< "Block " << case_target_id
|
||||
<< " is declared as the switch case target for two OpSwitch "
|
||||
"instructions, at blocks "
|
||||
<< case_block->case_head_for->begin_id << " and "
|
||||
<< construct->begin_id;
|
||||
}
|
||||
case_block->case_head_for = construct.get();
|
||||
}
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
bool FunctionEmitter::EmitFunctionVariables() {
|
||||
if (failed()) {
|
||||
return false;
|
||||
|
|
|
@ -70,6 +70,23 @@ struct BlockInfo {
|
|||
|
||||
/// The immediately enclosing structured construct.
|
||||
const Construct* construct = nullptr;
|
||||
|
||||
/// The following fields record relationships among blocks in a selection
|
||||
/// construct for an OpSwitch instruction.
|
||||
|
||||
/// If not null, then the pointed-at construct is a selection for an OpSwitch,
|
||||
/// and this block is a case target for it. We say this block "heads" the
|
||||
/// case construct.
|
||||
const Construct* case_head_for = nullptr;
|
||||
/// If not null, then the pointed-at construct is a selection for an OpSwitch,
|
||||
/// and this block is the default target for it. We say this block "heads"
|
||||
/// the default case construct.
|
||||
const Construct* default_head_for = nullptr;
|
||||
/// Is this a default target for a switch, and is it also the merge for its
|
||||
/// switch?
|
||||
bool default_is_merge = false;
|
||||
/// The list of switch values that cause a branch to this block.
|
||||
std::unique_ptr<std::vector<uint64_t>> case_values;
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) {
|
||||
|
@ -162,6 +179,11 @@ class FunctionEmitter {
|
|||
/// @returns the structured constructs
|
||||
const ConstructList& constructs() const { return constructs_; }
|
||||
|
||||
/// Marks blocks targets of a switch, either as the head of a case or
|
||||
/// as the default target.
|
||||
/// @returns false on failure
|
||||
bool FindSwitchCaseHeaders();
|
||||
|
||||
/// Emits declarations of function variables.
|
||||
/// @returns false if emission failed.
|
||||
bool EmitFunctionVariables();
|
||||
|
|
|
@ -39,6 +39,7 @@ std::string Dump(const std::vector<uint32_t>& v) {
|
|||
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::Eq;
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
std::string CommonTypes() {
|
||||
return R"(
|
||||
|
@ -3540,6 +3541,624 @@ TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_If_MultiBlockLoop) {
|
|||
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_DefaultIsLongRangeBackedge) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpBranch %20
|
||||
|
||||
%20 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %10 30 %30
|
||||
|
||||
%30 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
||||
EXPECT_THAT(p->error(), Eq("Switch branch from block 20 to default target "
|
||||
"block 10 can't be a back-edge"));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_DefaultIsSelfLoop) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpBranch %20
|
||||
|
||||
%20 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %20 30 %30
|
||||
|
||||
%30 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
||||
// Self-loop that isn't its own continue target is already rejected with a
|
||||
// different message.
|
||||
EXPECT_THAT(
|
||||
p->error(),
|
||||
Eq("Block 20 branches to itself but is not its own continue target"));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_DefaultCantEscapeSwitch) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %50 None
|
||||
OpSwitch %selector %99 30 %30 ; default goes past the merge
|
||||
|
||||
%30 = OpLabel
|
||||
OpBranch %50
|
||||
|
||||
%50 = OpLabel ; merge
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
||||
EXPECT_THAT(p->error(), Eq("Switch branch from block 10 to default block 99 "
|
||||
"escapes the selection construct"));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_DefaultForTwoSwitches) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %89 20 %20
|
||||
|
||||
%20 = OpLabel
|
||||
OpBranch %50
|
||||
|
||||
%50 = OpLabel
|
||||
OpSelectionMerge %89 None
|
||||
OpSwitch %selector %89 60 %60
|
||||
|
||||
%60 = OpLabel
|
||||
OpBranch %89
|
||||
|
||||
%89 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
||||
EXPECT_THAT(p->error(), Eq("Block 89 is declared as the default target for "
|
||||
"two OpSwitch instructions, at blocks 10 and 50"));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_CaseIsLongRangeBackedge) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpBranch %20
|
||||
|
||||
%20 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %99 10 %10
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
||||
EXPECT_THAT(p->error(), Eq("Switch branch from block 20 to case target "
|
||||
"block 10 can't be a back-edge"));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_CaseIsSelfLoop) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpBranch %20
|
||||
|
||||
%20 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %99 20 %20
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
||||
// The error is caught earlier
|
||||
EXPECT_THAT(
|
||||
p->error(),
|
||||
Eq("Block 20 branches to itself but is not its own continue target"));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_CaseCanBeSwitchMerge) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpBranch %20
|
||||
|
||||
%20 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %99 20 %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_TRUE(fe.FindSwitchCaseHeaders());
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_CaseCantEscapeSwitch) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %99 None ; force %99 to be very late in block order
|
||||
OpBranchConditional %cond %20 %99
|
||||
|
||||
%20 = OpLabel
|
||||
OpSelectionMerge %89 None
|
||||
OpSwitch %selector %89 20 %99
|
||||
|
||||
%89 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
||||
EXPECT_THAT(p->error(), Eq("Switch branch from block 20 to case target block "
|
||||
"99 escapes the selection construct"));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_CaseForMoreThanOneSwitch) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %99 20 %20 50 %50
|
||||
|
||||
%20 = OpLabel
|
||||
OpSelectionMerge %89 None
|
||||
OpSwitch %selector %89 50 %50
|
||||
|
||||
%50 = OpLabel
|
||||
OpBranch %89
|
||||
|
||||
%89 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
||||
EXPECT_THAT(p->error(),
|
||||
Eq("Block 50 is declared as the switch case target for two "
|
||||
"OpSwitch instructions, at blocks 10 and 20"));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_CaseIsMergeForAnotherConstruct) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %49 None
|
||||
OpSwitch %selector %49 20 %20
|
||||
|
||||
%20 = OpLabel
|
||||
OpBranch %49
|
||||
|
||||
%49 = OpLabel
|
||||
OpBranch %50
|
||||
|
||||
%50 = OpLabel
|
||||
OpSelectionMerge %20 None ; points back to the case.
|
||||
OpBranchConditional %cond %60 %99
|
||||
|
||||
%60 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
||||
EXPECT_THAT(p->error(), Eq("Switch branch from block 10 to case target block "
|
||||
"20 escapes the selection construct"));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_NoSwitch) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_TRUE(fe.FindSwitchCaseHeaders());
|
||||
|
||||
const auto* bi10 = fe.GetBlockInfo(10);
|
||||
ASSERT_NE(bi10, nullptr);
|
||||
EXPECT_EQ(bi10->case_head_for, nullptr);
|
||||
EXPECT_EQ(bi10->default_head_for, nullptr);
|
||||
EXPECT_FALSE(bi10->default_is_merge);
|
||||
EXPECT_EQ(bi10->case_values.get(), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_DefaultIsMerge) {
|
||||
auto assembly = 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
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_TRUE(fe.FindSwitchCaseHeaders());
|
||||
|
||||
const auto* bi99 = fe.GetBlockInfo(99);
|
||||
ASSERT_NE(bi99, nullptr);
|
||||
EXPECT_EQ(bi99->case_head_for, nullptr);
|
||||
ASSERT_NE(bi99->default_head_for, nullptr);
|
||||
EXPECT_EQ(bi99->default_head_for->begin_id, 10);
|
||||
EXPECT_TRUE(bi99->default_is_merge);
|
||||
EXPECT_EQ(bi99->case_values.get(), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_DefaultIsNotMerge) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %30 20 %20
|
||||
|
||||
%20 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%30 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_TRUE(fe.FindSwitchCaseHeaders());
|
||||
|
||||
const auto* bi30 = fe.GetBlockInfo(30);
|
||||
ASSERT_NE(bi30, nullptr);
|
||||
EXPECT_EQ(bi30->case_head_for, nullptr);
|
||||
ASSERT_NE(bi30->default_head_for, nullptr);
|
||||
EXPECT_EQ(bi30->default_head_for->begin_id, 10);
|
||||
EXPECT_FALSE(bi30->default_is_merge);
|
||||
EXPECT_EQ(bi30->case_values.get(), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_CaseIsNotDefault) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %30 200 %20
|
||||
|
||||
%20 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%30 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_TRUE(fe.FindSwitchCaseHeaders());
|
||||
|
||||
const auto* bi20 = fe.GetBlockInfo(20);
|
||||
ASSERT_NE(bi20, nullptr);
|
||||
ASSERT_NE(bi20->case_head_for, nullptr);
|
||||
EXPECT_EQ(bi20->case_head_for->begin_id, 10);
|
||||
EXPECT_EQ(bi20->default_head_for, nullptr);
|
||||
EXPECT_FALSE(bi20->default_is_merge);
|
||||
EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_CaseIsDefault) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %20 200 %20
|
||||
|
||||
%20 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_TRUE(fe.FindSwitchCaseHeaders());
|
||||
|
||||
const auto* bi20 = fe.GetBlockInfo(20);
|
||||
ASSERT_NE(bi20, nullptr);
|
||||
ASSERT_NE(bi20->case_head_for, nullptr);
|
||||
EXPECT_EQ(bi20->case_head_for->begin_id, 10);
|
||||
EXPECT_EQ(bi20->default_head_for, bi20->case_head_for);
|
||||
EXPECT_FALSE(bi20->default_is_merge);
|
||||
EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_ManyCasesWithSameValue_Error) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %99 200 %20 200 %30
|
||||
|
||||
%20 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%30 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
||||
|
||||
EXPECT_THAT(p->error(),
|
||||
Eq("Duplicate case value 200 in OpSwitch in block 10"));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, FindSwitchCaseHeaders_ManyValuesWithSameCase) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpSwitch %selector %99 200 %20 300 %20
|
||||
|
||||
%20 = OpLabel
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
EXPECT_TRUE(fe.FindSwitchCaseHeaders());
|
||||
|
||||
const auto* bi20 = fe.GetBlockInfo(20);
|
||||
ASSERT_NE(bi20, nullptr);
|
||||
ASSERT_NE(bi20->case_head_for, nullptr);
|
||||
EXPECT_EQ(bi20->case_head_for->begin_id, 10);
|
||||
EXPECT_EQ(bi20->default_head_for, nullptr);
|
||||
EXPECT_FALSE(bi20->default_is_merge);
|
||||
EXPECT_THAT(*(bi20->case_values.get()), UnorderedElementsAre(200, 300));
|
||||
}
|
||||
|
||||
TEST_F(SpvParserTest, DISABLED_BranchEscapesIfConstruct) {
|
||||
auto assembly = CommonTypes() + R"(
|
||||
%100 = OpFunction %void None %voidfn
|
||||
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %99 None
|
||||
OpBranchConditional %cond %20 %99
|
||||
|
||||
%20 = OpLabel
|
||||
OpSelectionMerge %50 None
|
||||
OpBranchConditional %cond2 %30 %50
|
||||
|
||||
%30 = OpLabel
|
||||
OpBranch %80 ; bad exit to %80
|
||||
|
||||
%50 = OpLabel
|
||||
OpBranch %80
|
||||
|
||||
%80 = OpLabel ; bad target
|
||||
OpBranch %99
|
||||
|
||||
%99 = OpLabel
|
||||
OpReturn
|
||||
)";
|
||||
auto* p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||
FunctionEmitter fe(p, *spirv_function(100));
|
||||
fe.RegisterBasicBlocks();
|
||||
fe.ComputeBlockOrderAndPositions();
|
||||
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
||||
fe.RegisterMerges();
|
||||
fe.LabelControlFlowConstructs();
|
||||
fe.FindSwitchCaseHeaders();
|
||||
// Some further processing
|
||||
EXPECT_THAT(p->error(), Eq("something"));
|
||||
}
|
||||
|
||||
// TODO(dneto): Ok for a case target to branch directly to the merge
|
||||
// TODO(dneto): Ok for a case target to be a "break block", i.e. branch to exit
|
||||
// of enclosing loop.
|
||||
// TODO(dneto): Ok for a case target to be a "continue block", i.e. branch to
|
||||
// continue target of enclosing loop.
|
||||
|
||||
} // namespace
|
||||
} // namespace spirv
|
||||
} // namespace reader
|
||||
|
|
Loading…
Reference in New Issue