[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()) {
|
if (!LabelControlFlowConstructs()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!FindSwitchCaseHeaders()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!EmitFunctionVariables()) {
|
if (!EmitFunctionVariables()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -806,6 +809,111 @@ bool FunctionEmitter::LabelControlFlowConstructs() {
|
||||||
return success();
|
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() {
|
bool FunctionEmitter::EmitFunctionVariables() {
|
||||||
if (failed()) {
|
if (failed()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -70,6 +70,23 @@ struct BlockInfo {
|
||||||
|
|
||||||
/// The immediately enclosing structured construct.
|
/// The immediately enclosing structured construct.
|
||||||
const Construct* construct = nullptr;
|
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) {
|
inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) {
|
||||||
|
@ -162,6 +179,11 @@ class FunctionEmitter {
|
||||||
/// @returns the structured constructs
|
/// @returns the structured constructs
|
||||||
const ConstructList& constructs() const { return 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.
|
/// Emits declarations of function variables.
|
||||||
/// @returns false if emission failed.
|
/// @returns false if emission failed.
|
||||||
bool EmitFunctionVariables();
|
bool EmitFunctionVariables();
|
||||||
|
|
|
@ -39,6 +39,7 @@ std::string Dump(const std::vector<uint32_t>& v) {
|
||||||
|
|
||||||
using ::testing::ElementsAre;
|
using ::testing::ElementsAre;
|
||||||
using ::testing::Eq;
|
using ::testing::Eq;
|
||||||
|
using ::testing::UnorderedElementsAre;
|
||||||
|
|
||||||
std::string CommonTypes() {
|
std::string CommonTypes() {
|
||||||
return R"(
|
return R"(
|
||||||
|
@ -3540,6 +3541,624 @@ TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_If_MultiBlockLoop) {
|
||||||
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
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
|
||||||
} // namespace spirv
|
} // namespace spirv
|
||||||
} // namespace reader
|
} // namespace reader
|
||||||
|
|
Loading…
Reference in New Issue