mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-07-03 11:46:09 +00:00
Handle the case where the OpBranchConditional in a loop header branches to two distinct blocks inside the loop construct. This is an if-selection in disguise. Create an kIfSelection with the same set of blocks as the kLoop, and with the continue target as the merge. Fixed: tint:524 Change-Id: I5150d19a2b4388da409e2da6e68ffafdc5d21a9a Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/47560 Commit-Queue: David Neto <dneto@google.com> Auto-Submit: David Neto <dneto@google.com> Reviewed-by: Alan Baker <alanbaker@google.com>
14399 lines
356 KiB
C++
14399 lines
356 KiB
C++
// Copyright 2020 The Tint Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "src/reader/spirv/function.h"
|
|
#include "src/reader/spirv/parser_impl_test_helper.h"
|
|
#include "src/reader/spirv/spirv_tools_helpers_test.h"
|
|
|
|
namespace tint {
|
|
namespace reader {
|
|
namespace spirv {
|
|
namespace {
|
|
|
|
using ::testing::Eq;
|
|
using ::testing::HasSubstr;
|
|
|
|
std::string Dump(const std::vector<uint32_t>& v) {
|
|
std::ostringstream o;
|
|
o << "{";
|
|
for (auto a : v) {
|
|
o << a << " ";
|
|
}
|
|
o << "}";
|
|
return o.str();
|
|
}
|
|
|
|
using ::testing::ElementsAre;
|
|
using ::testing::Eq;
|
|
using ::testing::UnorderedElementsAre;
|
|
|
|
std::string CommonTypes() {
|
|
return R"(
|
|
OpCapability Shader
|
|
OpMemoryModel Logical Simple
|
|
|
|
OpName %var "var"
|
|
|
|
%void = OpTypeVoid
|
|
%voidfn = OpTypeFunction %void
|
|
|
|
%bool = OpTypeBool
|
|
%cond = OpConstantNull %bool
|
|
%cond2 = OpConstantTrue %bool
|
|
%cond3 = OpConstantFalse %bool
|
|
|
|
%uint = OpTypeInt 32 0
|
|
%int = OpTypeInt 32 1
|
|
%selector = OpConstant %uint 42
|
|
%signed_selector = OpConstant %int 42
|
|
|
|
%uintfn = OpTypeFunction %uint
|
|
|
|
%uint_0 = OpConstant %uint 0
|
|
%uint_1 = OpConstant %uint 1
|
|
%uint_2 = OpConstant %uint 2
|
|
%uint_3 = OpConstant %uint 3
|
|
%uint_4 = OpConstant %uint 4
|
|
%uint_5 = OpConstant %uint 5
|
|
%uint_6 = OpConstant %uint 6
|
|
%uint_7 = OpConstant %uint 7
|
|
%uint_8 = OpConstant %uint 8
|
|
%uint_10 = OpConstant %uint 10
|
|
%uint_20 = OpConstant %uint 20
|
|
%uint_30 = OpConstant %uint 30
|
|
%uint_40 = OpConstant %uint 40
|
|
%uint_50 = OpConstant %uint 50
|
|
%uint_90 = OpConstant %uint 90
|
|
%uint_99 = OpConstant %uint 99
|
|
|
|
%ptr_Private_uint = OpTypePointer Private %uint
|
|
%var = OpVariable %ptr_Private_uint Private
|
|
|
|
%999 = OpConstant %uint 999
|
|
)";
|
|
}
|
|
|
|
/// Runs the necessary flow until and including labeling control
|
|
/// flow constructs.
|
|
/// @returns the result of labeling control flow constructs.
|
|
bool FlowLabelControlFlowConstructs(FunctionEmitter* fe) {
|
|
fe->RegisterBasicBlocks();
|
|
EXPECT_TRUE(fe->RegisterMerges()) << fe->parser()->error();
|
|
fe->ComputeBlockOrderAndPositions();
|
|
EXPECT_TRUE(fe->VerifyHeaderContinueMergeOrder()) << fe->parser()->error();
|
|
return fe->LabelControlFlowConstructs();
|
|
}
|
|
|
|
/// Runs the necessary flow until and including finding switch case
|
|
/// headers.
|
|
/// @returns the result of finding switch case headers.
|
|
bool FlowFindSwitchCaseHeaders(FunctionEmitter* fe) {
|
|
EXPECT_TRUE(FlowLabelControlFlowConstructs(fe)) << fe->parser()->error();
|
|
return fe->FindSwitchCaseHeaders();
|
|
}
|
|
|
|
/// Runs the necessary flow until and including classify CFG edges,
|
|
/// @returns the result of classify CFG edges.
|
|
bool FlowClassifyCFGEdges(FunctionEmitter* fe) {
|
|
EXPECT_TRUE(FlowFindSwitchCaseHeaders(fe)) << fe->parser()->error();
|
|
return fe->ClassifyCFGEdges();
|
|
}
|
|
|
|
/// Runs the necessary flow until and including finding if-selection
|
|
/// internal headers.
|
|
/// @returns the result of classify CFG edges.
|
|
bool FlowFindIfSelectionInternalHeaders(FunctionEmitter* fe) {
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(fe)) << fe->parser()->error();
|
|
return fe->FindIfSelectionInternalHeaders();
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_SingleBlock) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%42 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_TRUE(fe.TerminatorsAreValid());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_Sequence) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%20 = OpLabel
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_TRUE(fe.TerminatorsAreValid()) << p->error();
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_If) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %30 %40
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_TRUE(fe.TerminatorsAreValid()) << p->error();
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_Switch) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %80 20 %20 30 %30
|
|
|
|
%20 = OpLabel
|
|
OpBranch %30 ; fall through
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%80 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_TRUE(fe.TerminatorsAreValid());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_Loop_SingleBlock) {
|
|
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.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_TRUE(fe.TerminatorsAreValid());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_Loop_Simple) {
|
|
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 ; back edge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_TRUE(fe.TerminatorsAreValid());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_Kill) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpKill
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_TRUE(fe.TerminatorsAreValid());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_Unreachable) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpUnreachable
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_TRUE(fe.TerminatorsAreValid());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_MissingTerminator) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
// The SPIRV-Tools internal representation rejects this case earlier.
|
|
EXPECT_FALSE(p->BuildAndParseInternalModuleExceptFunctions());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_DisallowLoopToEntryBlock) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpBranch %10 ; not allowed
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_FALSE(fe.TerminatorsAreValid());
|
|
EXPECT_THAT(p->error(), Eq("Block 20 branches to function entry block 10"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_DisallowNonBlock) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %999 ; definitely wrong
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_FALSE(fe.TerminatorsAreValid());
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Block 10 in function 100 branches to 999 which is "
|
|
"not a block in the function"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, TerminatorsAreValid_DisallowBlockInDifferentFunction) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %210
|
|
|
|
OpFunctionEnd
|
|
|
|
|
|
%200 = OpFunction %void None %voidfn
|
|
|
|
%210 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_FALSE(fe.TerminatorsAreValid());
|
|
EXPECT_THAT(p->error(), Eq("Block 10 in function 100 branches to 210 which "
|
|
"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.get(), *spirv_function(p.get(), 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->is_continue_entire_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.get(), *spirv_function(p.get(), 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->is_continue_entire_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->is_continue_entire_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->is_continue_entire_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.get(), *spirv_function(p.get(), 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->is_continue_entire_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->is_continue_entire_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->is_continue_entire_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.get(), *spirv_function(p.get(), 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->is_continue_entire_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->is_continue_entire_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->is_continue_entire_loop);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsHeader) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %20 None
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_TRUE(fe.RegisterMerges());
|
|
|
|
// Loop header points to continue (itself) 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, 20u);
|
|
EXPECT_EQ(bi20->header_for_merge, 0u);
|
|
EXPECT_EQ(bi20->header_for_continue, 20u);
|
|
EXPECT_TRUE(bi20->is_continue_entire_loop);
|
|
|
|
// Backedge block, but is not a declared header, merge, or continue
|
|
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, 0u);
|
|
EXPECT_FALSE(bi40->is_continue_entire_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->is_continue_entire_loop);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_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.get(), *spirv_function(p.get(), 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->is_continue_entire_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->is_continue_entire_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->is_continue_entire_loop);
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_BranchConditional) { // NOLINT
|
|
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.get(), *spirv_function(p.get(), 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->is_continue_entire_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->is_continue_entire_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->is_continue_entire_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 %20
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 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 %99
|
|
|
|
%40 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
EXPECT_FALSE(fe.RegisterMerges());
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Structured header block 10 declares invalid merge block 2"));
|
|
}
|
|
|
|
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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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, ComputeBlockOrder_OneBlock) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%42 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(42));
|
|
|
|
const auto* bi = fe.GetBlockInfo(42);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->pos, 0u);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_IgnoreStaticalyUnreachable) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%15 = OpLabel ; statically dead
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_KillIsDeadEnd) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%15 = OpLabel ; statically dead
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpKill ; Kill doesn't lead anywhere
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_UnreachableIsDeadEnd) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%15 = OpLabel ; statically dead
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpUnreachable ; Unreachable doesn't lead anywhere
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_ReorderSequence) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%30 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpBranch %30 ; backtrack
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30));
|
|
|
|
const auto* bi10 = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi10, nullptr);
|
|
EXPECT_EQ(bi10->pos, 0u);
|
|
const auto* bi20 = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi20, nullptr);
|
|
EXPECT_EQ(bi20->pos, 1u);
|
|
const auto* bi30 = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi30, nullptr);
|
|
EXPECT_EQ(bi30->pos, 2u);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_DupConditionalBranch) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_RespectConditionalBranchOrder) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %30
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%30 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_TrueOnlyBranch) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_FalseOnlyBranch) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %99 %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_SwitchOrderNaturallyReversed) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 30 %30
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%30 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ComputeBlockOrder_SwitchWithDefaultOrderNaturallyReversed) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %80 20 %20 30 %30
|
|
|
|
%80 = OpLabel ; the default case
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%30 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 80, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Switch_DefaultSameAsACase) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %30 20 %20 30 %30 40 %40
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 40, 20, 30, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_RespectSwitchCaseFallthrough) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 30 %30 40 %40 50 %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%40 = OpLabel
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50 ; fallthrough
|
|
|
|
%20 = OpLabel
|
|
OpBranch %40 ; fallthrough
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 50, 20, 40, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ComputeBlockOrder_RespectSwitchCaseFallthrough_FromDefault) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %80 20 %20 30 %30 40 %40
|
|
|
|
%80 = OpLabel ; the default case
|
|
OpBranch %30 ; fallthrough to another case
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%40 = OpLabel
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 30, 40, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ComputeBlockOrder_RespectSwitchCaseFallthrough_FromCaseToDefaultToCase) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %80 20 %20 30 %30
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpBranch %80 ; fallthrough to default
|
|
|
|
%80 = OpLabel ; the default case
|
|
OpBranch %30 ; fallthrough to 30
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 30, 99)) << assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ComputeBlockOrder_SwitchCasesFallthrough_OppositeDirections) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 30 %30 40 %40 50 %50
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpBranch %30 ; forward
|
|
|
|
%40 = OpLabel
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
; SPIR-V doesn't actually allow a fall-through that goes backward in the
|
|
; module. But the block ordering algorithm tolerates it.
|
|
%50 = OpLabel
|
|
OpBranch %40 ; backward
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 50, 40, 20, 30, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ComputeBlockOrder_RespectSwitchCaseFallthrough_Interleaved) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 30 %30 40 %40 50 %50
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpBranch %40
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50
|
|
|
|
%40 = OpLabel
|
|
OpBranch %60
|
|
|
|
%50 = OpLabel
|
|
OpBranch %70
|
|
|
|
%60 = OpLabel
|
|
OpBranch %99
|
|
|
|
%70 = OpLabel
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 50, 70, 20, 40, 60, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Nest_If_Contains_If) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %50
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond %30 %40
|
|
|
|
%49 = OpLabel
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %49
|
|
|
|
%40 = OpLabel
|
|
OpBranch %49
|
|
|
|
%50 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpBranchConditional %cond %60 %70
|
|
|
|
%79 = OpLabel
|
|
OpBranch %99
|
|
|
|
%60 = OpLabel
|
|
OpBranch %79
|
|
|
|
%70 = OpLabel
|
|
OpBranch %79
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(),
|
|
ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Nest_If_In_SwitchCase) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %50 20 %20 50 %50
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond %30 %40
|
|
|
|
%49 = OpLabel
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %49
|
|
|
|
%40 = OpLabel
|
|
OpBranch %49
|
|
|
|
%50 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpBranchConditional %cond %60 %70
|
|
|
|
%79 = OpLabel
|
|
OpBranch %99
|
|
|
|
%60 = OpLabel
|
|
OpBranch %79
|
|
|
|
%70 = OpLabel
|
|
OpBranch %79
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(),
|
|
ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Nest_IfFallthrough_In_SwitchCase) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %50 20 %20 50 %50
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond %30 %40
|
|
|
|
%49 = OpLabel
|
|
OpBranchConditional %cond %99 %50 ; fallthrough
|
|
|
|
%30 = OpLabel
|
|
OpBranch %49
|
|
|
|
%40 = OpLabel
|
|
OpBranch %49
|
|
|
|
%50 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpBranchConditional %cond %60 %70
|
|
|
|
%79 = OpLabel
|
|
OpBranch %99
|
|
|
|
%60 = OpLabel
|
|
OpBranch %79
|
|
|
|
%70 = OpLabel
|
|
OpBranch %79
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(),
|
|
ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Nest_IfBreak_In_SwitchCase) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %50 20 %20 50 %50
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond %99 %40 ; break-if
|
|
|
|
%49 = OpLabel
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel
|
|
OpBranch %49
|
|
|
|
%50 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpBranchConditional %cond %60 %99 ; break-unless
|
|
|
|
%79 = OpLabel
|
|
OpBranch %99
|
|
|
|
%60 = OpLabel
|
|
OpBranch %79
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 49, 50, 60, 79, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_SingleBlock_Simple) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
; The entry block can't be the target of a branch
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_SingleBlock_Infinite) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
; The entry block can't be the target of a branch
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %20 None
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_SingleBlock_DupInfinite) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
; The entry block can't be the target of a branch
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %20 %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_HeaderHasBreakIf) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99 ; like While
|
|
|
|
%30 = OpLabel ; trivial body
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_HeaderHasBreakUnless) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %99 %30 ; has break-unless
|
|
|
|
%30 = OpLabel ; trivial body
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasBreak) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99 ; break
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasBreakIf) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranchConditional %cond2 %99 %40 ; break-if
|
|
|
|
%40 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasBreakUnless) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranchConditional %cond2 %40 %99 ; break-unless
|
|
|
|
%40 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_If) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond2 %40 %45 ; nested if
|
|
|
|
%40 = OpLabel
|
|
OpBranch %49
|
|
|
|
%45 = OpLabel
|
|
OpBranch %49
|
|
|
|
%49 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 45, 49, 50, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_If_Break) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond2 %40 %49 ; nested if
|
|
|
|
%40 = OpLabel
|
|
OpBranch %99 ; break from nested if
|
|
|
|
%49 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasContinueIf) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranchConditional %cond2 %50 %40 ; continue-if
|
|
|
|
%40 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasContinueUnless) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranchConditional %cond2 %40 %50 ; continue-unless
|
|
|
|
%40 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_If_Continue) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond2 %40 %49 ; nested if
|
|
|
|
%40 = OpLabel
|
|
OpBranch %50 ; continue from nested if
|
|
|
|
%49 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_Switch) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpSwitch %selector %49 40 %40 45 %45 ; fully nested switch
|
|
|
|
%40 = OpLabel
|
|
OpBranch %49
|
|
|
|
%45 = OpLabel
|
|
OpBranch %49
|
|
|
|
%49 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_Switch_CaseBreaks) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpSwitch %selector %49 40 %40 45 %45
|
|
|
|
%40 = OpLabel
|
|
; This case breaks out of the loop. This is not possible in C
|
|
; because "break" will escape the switch only.
|
|
OpBranch %99
|
|
|
|
%45 = OpLabel
|
|
OpBranch %49
|
|
|
|
%49 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_Switch_CaseContinues) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpSwitch %selector %49 40 %40 45 %45
|
|
|
|
%40 = OpLabel
|
|
OpBranch %50 ; continue bypasses switch merge
|
|
|
|
%45 = OpLabel
|
|
OpBranch %49
|
|
|
|
%49 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99))
|
|
<< assembly;
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasSwitchContinueBreak) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSwitch %selector %99 50 %50 ; default is break, 50 is continue
|
|
|
|
%40 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Continue_Sequence) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %60
|
|
|
|
%60 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 60, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Continue_ContainsIf) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpSelectionMerge %89 None
|
|
OpBranchConditional %cond2 %60 %70
|
|
|
|
%89 = OpLabel
|
|
OpBranch %20 ; backedge
|
|
|
|
%60 = OpLabel
|
|
OpBranch %89
|
|
|
|
%70 = OpLabel
|
|
OpBranch %89
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 60, 70, 89, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Continue_HasBreakIf) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranchConditional %cond2 %99 %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Continue_HasBreakUnless) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranchConditional %cond2 %20 %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Continue_SwitchBreak) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpSwitch %selector %20 99 %99 ; yes, this is obtuse but valid
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpLoopMerge %49 %40 None
|
|
OpBranchConditional %cond2 %35 %49
|
|
|
|
%35 = OpLabel
|
|
OpBranch %37
|
|
|
|
%37 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; inner loop's continue
|
|
OpBranch %30 ; backedge
|
|
|
|
%49 = OpLabel ; inner loop's merge
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; outer loop's continue
|
|
OpBranch %20 ; outer loop's backege
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(),
|
|
ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop_InnerBreak) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpLoopMerge %49 %40 None
|
|
OpBranchConditional %cond2 %35 %49
|
|
|
|
%35 = OpLabel
|
|
OpBranchConditional %cond3 %49 %37 ; break to inner merge
|
|
|
|
%37 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; inner loop's continue
|
|
OpBranch %30 ; backedge
|
|
|
|
%49 = OpLabel ; inner loop's merge
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; outer loop's continue
|
|
OpBranch %20 ; outer loop's backege
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(),
|
|
ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop_InnerContinue) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpLoopMerge %49 %40 None
|
|
OpBranchConditional %cond2 %35 %49
|
|
|
|
%35 = OpLabel
|
|
OpBranchConditional %cond3 %37 %49 ; continue to inner continue target
|
|
|
|
%37 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; inner loop's continue
|
|
OpBranch %30 ; backedge
|
|
|
|
%49 = OpLabel ; inner loop's merge
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; outer loop's continue
|
|
OpBranch %20 ; outer loop's backege
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(),
|
|
ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop_InnerContinueBreaks) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpLoopMerge %49 %40 None
|
|
OpBranchConditional %cond2 %35 %49
|
|
|
|
%35 = OpLabel
|
|
OpBranch %37
|
|
|
|
%37 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; inner loop's continue
|
|
OpBranchConditional %cond3 %30 %49 ; backedge and inner break
|
|
|
|
%49 = OpLabel ; inner loop's merge
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; outer loop's continue
|
|
OpBranch %20 ; outer loop's backege
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(),
|
|
ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop_InnerContinueContinues) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpLoopMerge %49 %40 None
|
|
OpBranchConditional %cond2 %35 %49
|
|
|
|
%35 = OpLabel
|
|
OpBranch %37
|
|
|
|
%37 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; inner loop's continue
|
|
OpBranchConditional %cond3 %30 %50 ; backedge and continue to outer
|
|
|
|
%49 = OpLabel ; inner loop's merge
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; outer loop's continue
|
|
OpBranch %20 ; outer loop's backege
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(),
|
|
ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop_SwitchBackedgeBreakContinue) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpLoopMerge %49 %40 None
|
|
OpBranchConditional %cond2 %35 %49
|
|
|
|
%35 = OpLabel
|
|
OpBranch %37
|
|
|
|
%37 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; inner loop's continue
|
|
; This switch does triple duty:
|
|
; default -> backedge
|
|
; 49 -> loop break
|
|
; 49 -> inner loop break
|
|
; 50 -> outer loop continue
|
|
OpSwitch %selector %30 49 %49 50 %50
|
|
|
|
%49 = OpLabel ; inner loop's merge
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; outer loop's continue
|
|
OpBranch %20 ; outer loop's backege
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
|
|
EXPECT_THAT(fe.block_order(),
|
|
ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, VerifyHeaderContinueMergeOrder_Selection_Good) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %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.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, VerifyHeaderContinueMergeOrder_SingleBlockLoop_Good) {
|
|
auto assembly = 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
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder()) << p->error();
|
|
}
|
|
|
|
TEST_F(SpvParserTest, VerifyHeaderContinueMergeOrder_MultiBlockLoop_Good) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %30 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
VerifyHeaderContinueMergeOrder_HeaderDoesNotStrictlyDominateMerge) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpSelectionMerge %20 None ; this is backward
|
|
OpBranchConditional %cond2 %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.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_FALSE(fe.VerifyHeaderContinueMergeOrder());
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Header 50 does not strictly dominate its merge block 20"))
|
|
<< *fe.GetBlockInfo(50) << std::endl
|
|
<< *fe.GetBlockInfo(20) << std::endl
|
|
<< Dump(fe.block_order());
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
VerifyHeaderContinueMergeOrder_HeaderDoesNotStrictlyDominateContinueTarget) { // NOLINT
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpLoopMerge %99 %20 None ; this is backward
|
|
OpBranchConditional %cond %60 %99
|
|
|
|
%60 = OpLabel
|
|
OpBranch %50
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_FALSE(fe.VerifyHeaderContinueMergeOrder());
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Loop header 50 does not dominate its continue target 20"))
|
|
<< *fe.GetBlockInfo(50) << std::endl
|
|
<< *fe.GetBlockInfo(20) << std::endl
|
|
<< Dump(fe.block_order());
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
VerifyHeaderContinueMergeOrder_MergeInsideContinueTarget) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpLoopMerge %60 %70 None
|
|
OpBranchConditional %cond %60 %99
|
|
|
|
%60 = OpLabel
|
|
OpBranch %70
|
|
|
|
%70 = OpLabel
|
|
OpBranch %50
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_FALSE(fe.VerifyHeaderContinueMergeOrder());
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Merge block 60 for loop headed at block 50 appears at or "
|
|
"before the loop's continue construct headed by block 70"))
|
|
<< Dump(fe.block_order());
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
LabelControlFlowConstructs_OuterConstructIsFunction_SingleBlock) {
|
|
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.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
EXPECT_EQ(fe.constructs().size(), 1u);
|
|
auto& c = fe.constructs().front();
|
|
EXPECT_THAT(ToString(c), Eq("Construct{ Function [0,1) begin_id:10 end_id:0 "
|
|
"depth:0 parent:null }"));
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, c.get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
LabelControlFlowConstructs_OuterConstructIsFunction_MultiBlock) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %5
|
|
|
|
%5 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
EXPECT_EQ(fe.constructs().size(), 1u);
|
|
auto& c = fe.constructs().front();
|
|
EXPECT_THAT(ToString(c), Eq("Construct{ Function [0,2) begin_id:10 end_id:0 "
|
|
"depth:0 parent:null }"));
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, c.get());
|
|
EXPECT_EQ(fe.GetBlockInfo(5)->construct, c.get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
LabelControlFlowConstructs_FunctionIsOnlyIfSelectionAndItsMerge) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %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.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 2u);
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,4) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ IfSelection [0,3) begin_id:10 end_id:99 depth:1 parent:Function@10 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
LabelControlFlowConstructs_PaddingBlocksBeforeAndAfterStructuredConstruct) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%5 = OpLabel
|
|
OpBranch %10
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %30
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpBranch %200
|
|
|
|
%200 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 2u);
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,6) begin_id:5 end_id:0 depth:0 parent:null }
|
|
Construct{ IfSelection [1,4) begin_id:10 end_id:99 depth:1 parent:Function@5 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(5)->construct, constructs[0].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(200)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, LabelControlFlowConstructs_SwitchSelection) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %40 20 %20 30 %30
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 2u);
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ SwitchSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 in-c-l-s:SwitchSelection@10 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, LabelControlFlowConstructs_SingleBlockLoop) {
|
|
auto assembly = 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
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 2u);
|
|
// A single-block loop consists *only* of a continue target with one block in
|
|
// it.
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,3) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ Continue [1,2) begin_id:20 end_id:99 depth:1 parent:Function@10 in-c:Continue@20 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
LabelControlFlowConstructs_MultiBlockLoop_HeaderIsNotContinue) {
|
|
// In this case, we have a continue construct and a non-empty loop construct.
|
|
auto assembly = 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 %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ Continue [3,5) begin_id:40 end_id:99 depth:1 parent:Function@10 in-c:Continue@40 }
|
|
Construct{ Loop [1,3) begin_id:20 end_id:40 depth:1 parent:Function@10 scope:[1,5) in-l:Loop@20 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
LabelControlFlowConstructs_MultiBlockLoop_HeaderIsContinue) {
|
|
// In this case, we have only a continue construct and no loop construct.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ Continue [1,5) begin_id:20 end_id:99 depth:1 parent:Function@10 in-c:Continue@20 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
LabelControlFlowConstructs_MergeBlockIsAlsoSingleBlockLoop) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond %20 %50
|
|
|
|
%20 = OpLabel
|
|
OpBranch %50
|
|
|
|
; %50 is the merge block for the selection starting at 10,
|
|
; and its own continue target.
|
|
%50 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %50 %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 3u);
|
|
// A single-block loop consists *only* of a continue target with one block in
|
|
// it.
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,4) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ IfSelection [0,2) begin_id:10 end_id:50 depth:1 parent:Function@10 }
|
|
Construct{ Continue [2,3) begin_id:50 end_id:99 depth:1 parent:Function@10 in-c:Continue@50 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
LabelControlFlowConstructs_MergeBlockIsAlsoMultiBlockLoopHeader) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond %20 %50
|
|
|
|
%20 = OpLabel
|
|
OpBranch %50
|
|
|
|
; %50 is the merge block for the selection starting at 10,
|
|
; and a loop block header but not its own continue target.
|
|
%50 = OpLabel
|
|
OpLoopMerge %99 %60 None
|
|
OpBranchConditional %cond %60 %99
|
|
|
|
%60 = OpLabel
|
|
OpBranch %50
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 4u);
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ IfSelection [0,2) begin_id:10 end_id:50 depth:1 parent:Function@10 }
|
|
Construct{ Continue [3,4) begin_id:60 end_id:99 depth:1 parent:Function@10 in-c:Continue@60 }
|
|
Construct{ Loop [2,3) begin_id:50 end_id:60 depth:1 parent:Function@10 scope:[2,4) in-l:Loop@50 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_If_If) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %50
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %30 %40 ;; true only
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; merge for first inner "if"
|
|
OpBranch %49
|
|
|
|
%49 = OpLabel ; an extra padding block
|
|
OpBranch %99
|
|
|
|
%50 = OpLabel
|
|
OpSelectionMerge %89 None
|
|
OpBranchConditional %cond %89 %60 ;; false only
|
|
|
|
%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.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 4u);
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,9) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ IfSelection [0,8) begin_id:10 end_id:99 depth:1 parent:Function@10 }
|
|
Construct{ IfSelection [1,3) begin_id:20 end_id:40 depth:2 parent:IfSelection@10 }
|
|
Construct{ IfSelection [5,7) begin_id:50 end_id:89 depth:2 parent:IfSelection@10 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_Switch_If) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 50 %50
|
|
|
|
%20 = OpLabel ; if-then nested in case 20
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond %30 %49
|
|
|
|
%30 = OpLabel
|
|
OpBranch %49
|
|
|
|
%49 = OpLabel
|
|
OpBranch %99
|
|
|
|
%50 = OpLabel ; unles-then nested in case 50
|
|
OpSelectionMerge %89 None
|
|
OpBranchConditional %cond %89 %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.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 4u);
|
|
// The ordering among siblings depends on the computed block order.
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ SwitchSelection [0,7) begin_id:10 end_id:99 depth:1 parent:Function@10 in-c-l-s:SwitchSelection@10 }
|
|
Construct{ IfSelection [1,3) begin_id:50 end_id:89 depth:2 parent:SwitchSelection@10 in-c-l-s:SwitchSelection@10 }
|
|
Construct{ IfSelection [4,6) begin_id:20 end_id:49 depth:2 parent:SwitchSelection@10 in-c-l-s:SwitchSelection@10 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_If_Switch) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %89 None
|
|
OpSwitch %selector %89 20 %30
|
|
|
|
%30 = 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.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 3u);
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,5) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ IfSelection [0,4) begin_id:10 end_id:99 depth:1 parent:Function@10 }
|
|
Construct{ SwitchSelection [1,3) begin_id:20 end_id:89 depth:2 parent:IfSelection@10 in-c-l-s:SwitchSelection@20 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_Loop_Loop) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %89 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel ; single block loop
|
|
OpLoopMerge %40 %30 None
|
|
OpBranchConditional %cond2 %30 %40
|
|
|
|
%40 = OpLabel ; padding block
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; outer continue target
|
|
OpBranch %60
|
|
|
|
%60 = OpLabel
|
|
OpBranch %20
|
|
|
|
%89 = OpLabel ; outer merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 4u);
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,8) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ Continue [4,6) begin_id:50 end_id:89 depth:1 parent:Function@10 in-c:Continue@50 }
|
|
Construct{ Loop [1,4) begin_id:20 end_id:50 depth:1 parent:Function@10 scope:[1,6) in-l:Loop@20 }
|
|
Construct{ Continue [2,3) begin_id:30 end_id:40 depth:2 parent:Loop@20 in-l:Loop@20 in-c:Continue@30 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(60)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[0].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_Loop_If) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel ; If, nested in the loop construct
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond2 %40 %49
|
|
|
|
%40 = OpLabel
|
|
OpBranch %49
|
|
|
|
%49 = OpLabel ; merge for inner if
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 4u);
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ Continue [5,6) begin_id:80 end_id:99 depth:1 parent:Function@10 in-c:Continue@80 }
|
|
Construct{ Loop [1,5) begin_id:20 end_id:80 depth:1 parent:Function@10 scope:[1,6) in-l:Loop@20 }
|
|
Construct{ IfSelection [2,4) begin_id:30 end_id:49 depth:2 parent:Loop@20 in-l:Loop@20 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(80)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_LoopContinue_If) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %30 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel ; If, nested at the top of the continue construct head
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond2 %40 %49
|
|
|
|
%40 = OpLabel
|
|
OpBranch %49
|
|
|
|
%49 = OpLabel ; merge for inner if, backedge
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 4u);
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ Continue [2,5) begin_id:30 end_id:99 depth:1 parent:Function@10 in-c:Continue@30 }
|
|
Construct{ Loop [1,2) begin_id:20 end_id:30 depth:1 parent:Function@10 scope:[1,5) in-l:Loop@20 }
|
|
Construct{ IfSelection [2,4) begin_id:30 end_id:49 depth:2 parent:Continue@30 in-c:Continue@30 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(49)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_If_SingleBlockLoop) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %89 %20 None
|
|
OpBranchConditional %cond %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.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 3u);
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,4) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ IfSelection [0,3) begin_id:10 end_id:99 depth:1 parent:Function@10 }
|
|
Construct{ Continue [1,2) begin_id:20 end_id:89 depth:2 parent:IfSelection@10 in-c:Continue@20 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, LabelControlFlowConstructs_Nest_If_MultiBlockLoop) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel ; start loop body
|
|
OpLoopMerge %89 %40 None
|
|
OpBranchConditional %cond %30 %89
|
|
|
|
%30 = OpLabel ; body block
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; continue target
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; backedge block
|
|
OpBranch %20
|
|
|
|
%89 = OpLabel ; merge for the loop
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel ; merge for the if
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
fe.RegisterMerges();
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 4u);
|
|
EXPECT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,7) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ IfSelection [0,6) begin_id:10 end_id:99 depth:1 parent:Function@10 }
|
|
Construct{ Continue [3,5) begin_id:40 end_id:89 depth:2 parent:IfSelection@10 in-c:Continue@40 }
|
|
Construct{ Loop [1,3) begin_id:20 end_id:40 depth:2 parent:IfSelection@10 scope:[1,5) in-l:Loop@20 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(50)->construct, constructs[2].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(89)->construct, constructs[1].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(99)->construct, constructs[0].get());
|
|
}
|
|
|
|
TEST_F(SpvParserTest, LabelControlFlowConstructs_LoopInterallyDiverge) {
|
|
// In this case, insert a synthetic if-selection with the same blocks
|
|
// as the loop construct.
|
|
// crbug.com/tint/524
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %90 None
|
|
OpBranchConditional %cond %30 %40 ; divergence to distinct targets in the body
|
|
|
|
%30 = OpLabel
|
|
OpBranch %90
|
|
|
|
%40 = OpLabel
|
|
OpBranch %90
|
|
|
|
%90 = OpLabel ; continue target
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
|
|
const auto& constructs = fe.constructs();
|
|
EXPECT_EQ(constructs.size(), 4u);
|
|
ASSERT_THAT(ToString(constructs), Eq(R"(ConstructList{
|
|
Construct{ Function [0,6) begin_id:10 end_id:0 depth:0 parent:null }
|
|
Construct{ Continue [4,5) begin_id:90 end_id:99 depth:1 parent:Function@10 in-c:Continue@90 }
|
|
Construct{ Loop [1,4) begin_id:20 end_id:90 depth:1 parent:Function@10 scope:[1,5) in-l:Loop@20 }
|
|
Construct{ IfSelection [1,4) begin_id:20 end_id:90 depth:2 parent:Loop@20 in-l:Loop@20 }
|
|
})")) << constructs;
|
|
// The block records the nearest enclosing construct.
|
|
EXPECT_EQ(fe.GetBlockInfo(10)->construct, constructs[0].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(20)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(30)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(40)->construct, constructs[3].get());
|
|
EXPECT_EQ(fe.GetBlockInfo(90)->construct, constructs[1].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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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_AsMerge) {
|
|
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.get(), *spirv_function(p.get(), 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 the default block for switch-selection header 10 "
|
|
"and also the merge block for 50 (violates dominance rule)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
FindSwitchCaseHeaders_DefaultForTwoSwitches_AsCaseClause) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %80 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpSelectionMerge %89 None
|
|
OpSwitch %selector %80 60 %60
|
|
|
|
%60 = OpLabel
|
|
OpBranch %89 ; fallthrough
|
|
|
|
%80 = OpLabel ; default for both switches
|
|
OpBranch %89
|
|
|
|
%89 = OpLabel ; inner selection merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; outer selection mege
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
|
fe.RegisterMerges();
|
|
fe.LabelControlFlowConstructs();
|
|
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
|
EXPECT_THAT(p->error(), Eq("Block 80 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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, 10u);
|
|
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.get(), *spirv_function(p.get(), 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, 10u);
|
|
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.get(), *spirv_function(p.get(), 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, 10u);
|
|
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.get(), *spirv_function(p.get(), 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, 10u);
|
|
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_IsError) {
|
|
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.get(), *spirv_function(p.get(), 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.get(), *spirv_function(p.get(), 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, 10u);
|
|
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, ClassifyCFGEdges_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.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe)) << p->error();
|
|
// Some further processing
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 30 to block 80 is an invalid exit from construct "
|
|
"starting at block 20; branch bypasses merge block 50"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_ReturnInContinueConstruct) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel ; body
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpReturn
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe)) << p->error();
|
|
EXPECT_THAT(p->error(), Eq("Invalid function exit at block 50 from continue "
|
|
"construct starting at 50"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_KillInContinueConstruct) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel ; body
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpKill
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(), Eq("Invalid function exit at block 50 from continue "
|
|
"construct starting at 50"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_UnreachableInContinueConstruct) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel ; body
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpUnreachable
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(), Eq("Invalid function exit at block 50 from continue "
|
|
"construct starting at 50"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_BackEdge_NotInContinueConstruct) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel ; body
|
|
OpBranch %20 ; bad backedge
|
|
|
|
%50 = OpLabel ; continue target
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Invalid backedge (30->20): 30 is not in a continue construct"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_BackEdge_NotInLastBlockOfContinueConstruct) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel ; body
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; continue target
|
|
OpBranchConditional %cond %20 %60 ; bad branch to %20
|
|
|
|
%60 = OpLabel ; end of continue construct
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Invalid exit (50->20) from continue construct: 50 is not the "
|
|
"last block in the continue construct starting at 50 "
|
|
"(violates post-dominance rule)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_BackEdge_ToWrongHeader) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %89 %50 None
|
|
OpBranchConditional %cond %30 %89
|
|
|
|
%30 = OpLabel ; loop body
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; continue target
|
|
OpBranch %10
|
|
|
|
%89 = OpLabel ; inner merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(), Eq("Invalid backedge (50->10): does not branch to "
|
|
"the corresponding loop header, expected 20"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_BackEdge_SingleBlockLoop) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi20 = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi20, nullptr);
|
|
EXPECT_EQ(bi20->succ_edge.count(20), 1u);
|
|
EXPECT_EQ(bi20->succ_edge[20], EdgeKind::kBack);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_BackEdge_MultiBlockLoop_SingleBlockContinueConstruct) {
|
|
auto assembly = 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 ; continue target
|
|
OpBranch %20 ; good back edge
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi40 = fe.GetBlockInfo(40);
|
|
ASSERT_NE(bi40, nullptr);
|
|
EXPECT_EQ(bi40->succ_edge.count(20), 1u);
|
|
EXPECT_EQ(bi40->succ_edge[20], EdgeKind::kBack);
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsNotHeader) { // NOLINT
|
|
auto assembly = 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 ; continue target
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20 ; good back edge
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi50 = fe.GetBlockInfo(50);
|
|
ASSERT_NE(bi50, nullptr);
|
|
EXPECT_EQ(bi50->succ_edge.count(20), 1u);
|
|
EXPECT_EQ(bi50->succ_edge[20], EdgeKind::kBack);
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsHeader) { // NOLINT
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %20 None ; continue target
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranchConditional %cond %20 %99 ; good back edge
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe)) << p->error();
|
|
|
|
auto* bi50 = fe.GetBlockInfo(50);
|
|
ASSERT_NE(bi50, nullptr);
|
|
EXPECT_EQ(bi50->succ_edge.count(20), 1u);
|
|
EXPECT_EQ(bi50->succ_edge[20], EdgeKind::kBack);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_PrematureExitFromContinueConstruct) {
|
|
auto assembly = 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 ; continue construct
|
|
OpBranchConditional %cond2 %99 %50 ; invalid early exit
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20 ; back edge
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Invalid exit (40->99) from continue construct: 40 is not the "
|
|
"last block in the continue construct starting at 40 "
|
|
"(violates post-dominance rule)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_TrueBranch) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel ; single block loop
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %99 %20
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
|
|
EXPECT_EQ(bi->succ_edge.count(20), 1u);
|
|
EXPECT_EQ(bi->succ_edge[20], EdgeKind::kBack);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopBreak_FromLoopHeader_SingleBlockLoop_FalseBranch) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel ; single block loop
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
|
|
EXPECT_EQ(bi->succ_edge.count(20), 1u);
|
|
EXPECT_EQ(bi->succ_edge[20], EdgeKind::kBack);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopBreak_FromLoopHeader_MultiBlockLoop) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %30 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_LoopBreak_FromContinueConstructHeader) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %30 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel ; Single block continue construct
|
|
OpBranchConditional %cond2 %20 %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromIfHeader) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kIfBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromIfThenElse) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %50
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%50 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
// Then clause
|
|
auto* bi20 = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi20, nullptr);
|
|
EXPECT_EQ(bi20->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
|
|
|
|
// Else clause
|
|
auto* bi50 = fe.GetBlockInfo(50);
|
|
ASSERT_NE(bi50, nullptr);
|
|
EXPECT_EQ(bi50->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi50->succ_edge[99], EdgeKind::kIfBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_BypassesMerge_IsError) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond %20 %50
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%50 = OpLabel ; merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 20 to block 99 is an invalid exit from "
|
|
"construct starting at block 10; branch bypasses merge block 50"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_EscapeSwitchCase_IsError) {
|
|
// Code generation assumes that you can't have kCaseFallThrough and kIfBreak
|
|
// from the same OpBranchConditional.
|
|
// This checks one direction of that, where the IfBreak is shown it can't
|
|
// escape a switch case.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None ; Set up if-break to %99
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %80 None ; switch-selection
|
|
OpSwitch %selector %80 30 %30 40 %40
|
|
|
|
%30 = OpLabel ; first case
|
|
; branch to %99 would be an if-break, but it bypasess the switch merge
|
|
; Also has case fall-through
|
|
OpBranchConditional %cond2 %99 %40
|
|
|
|
%40 = OpLabel ; second case
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; switch-selection's merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; if-selection's merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 30 to block 99 is an invalid exit from "
|
|
"construct starting at block 20; branch bypasses merge block 80"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromSwitchCaseDirect) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %30 20 %99 ; directly to merge
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromSwitchCaseBody) {
|
|
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
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultBody) {
|
|
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
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromSwitchDefaultIsMerge) {
|
|
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
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromNestedIf_Unconditional) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %80 None
|
|
OpBranchConditional %cond %30 %80
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%80 = OpLabel ; inner merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromNestedIf_Conditional) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %80 None
|
|
OpBranchConditional %cond %30 %80
|
|
|
|
%30 = OpLabel
|
|
OpBranchConditional %cond2 %99 %80 ; break-if
|
|
|
|
%80 = OpLabel ; inner merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kSwitchBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_BypassesMerge_IsError) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %50 None
|
|
OpSwitch %selector %50 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99 ; invalid exit
|
|
|
|
%50 = OpLabel ; switch merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 20 to block 99 is an invalid exit from "
|
|
"construct starting at block 10; branch bypasses merge block 50"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromNestedLoop_IsError) {
|
|
// It's an error because the break can only go as far as the loop.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %80 %70 None
|
|
OpBranchConditional %cond %30 %80
|
|
|
|
%30 = OpLabel ; in loop construct
|
|
OpBranch %99 ; break
|
|
|
|
%70 = OpLabel
|
|
OpBranch %20
|
|
|
|
%80 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 30 to block 99 is an invalid exit from "
|
|
"construct starting at block 20; branch bypasses merge block 80"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_SwitchBreak_FromNestedSwitch_IsError) {
|
|
// It's an error because the break can only go as far as inner switch
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %80 None
|
|
OpSwitch %selector %80 30 %30
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99 ; break
|
|
|
|
%80 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 30 to block 99 is an invalid exit from "
|
|
"construct starting at block 20; branch bypasses merge block 80"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_LoopBreak_FromLoopBody) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranchConditional %cond2 %50 %99 ; break-unless
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_LoopBreak_FromContinueConstructTail) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; continue target
|
|
OpBranch %60
|
|
|
|
%60 = OpLabel ; continue construct tail
|
|
OpBranchConditional %cond2 %20 %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(60);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_LoopBreak_FromLoopBodyDirect) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99 ; unconditional break
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Unconditional) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond2 %40 %50
|
|
|
|
%40 = OpLabel
|
|
OpBranch %99 ; deeply nested break
|
|
|
|
%50 = OpLabel ; inner merge
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20 ; backedge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(40);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopBreak_FromLoopBodyNestedSelection_Conditional) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond2 %40 %50
|
|
|
|
%40 = OpLabel
|
|
OpBranchConditional %cond3 %99 %50 ; break-if
|
|
|
|
%50 = OpLabel ; inner merge
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20 ; backedge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(40);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi->succ_edge[99], EdgeKind::kLoopBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopBreak_FromContinueConstructNestedFlow_IsError) {
|
|
auto assembly = 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 ; continue construct
|
|
OpSelectionMerge %79 None
|
|
OpBranchConditional %cond2 %50 %79
|
|
|
|
%50 = OpLabel
|
|
OpBranchConditional %cond3 %99 %79 ; attempt to break to 99 should fail
|
|
|
|
%79 = OpLabel
|
|
OpBranch %80 ; inner merge
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20 ; backedge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Invalid exit (50->99) from continue construct: 50 is not the "
|
|
"last block in the continue construct starting at 40 "
|
|
"(violates post-dominance rule)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopBreak_FromLoopBypassesMerge_IsError) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %50 %40 None
|
|
OpBranchConditional %cond %30 %50
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99 ; bad exit
|
|
|
|
%40 = OpLabel ; continue construct
|
|
OpBranch %20
|
|
|
|
%50 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 30 to block 99 is an invalid exit from "
|
|
"construct starting at block 20; branch bypasses merge block 50"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopBreak_FromContinueBypassesMerge_IsError) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %50 %40 None
|
|
OpBranchConditional %cond %30 %50
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; continue construct
|
|
OpBranch %45
|
|
|
|
%45 = OpLabel
|
|
OpBranchConditional %cond2 %20 %99 ; branch to %99 is bad exit
|
|
|
|
%50 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 45 to block 99 is an invalid exit from "
|
|
"construct starting at block 40; branch bypasses merge block 50"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_LoopContinue_LoopBodyToContinue) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %80 ; a forward edge
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(80), 1u);
|
|
EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_LoopContinue_FromNestedIf) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpBranchConditional %cond2 %40 %79
|
|
|
|
%40 = OpLabel
|
|
OpBranch %80 ; continue
|
|
|
|
%79 = OpLabel ; inner merge
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(40);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(80), 1u);
|
|
EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_LoopContinue_ConditionalFromNestedIf) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpBranchConditional %cond2 %40 %79
|
|
|
|
%40 = OpLabel
|
|
OpBranchConditional %cond2 %80 %79 ; continue-if
|
|
|
|
%79 = OpLabel ; inner merge
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(40);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(80), 1u);
|
|
EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopContinue_FromNestedSwitchCaseBody_Unconditional) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %79 40 %40
|
|
|
|
%40 = OpLabel
|
|
OpBranch %80
|
|
|
|
%79 = OpLabel ; inner merge
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe)) << p->error();
|
|
|
|
auto* bi = fe.GetBlockInfo(40);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(80), 1u);
|
|
EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopContinue_FromNestedSwitchCaseDirect_IsError) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %79 40 %80 ; continue here
|
|
|
|
%79 = OpLabel ; inner merge
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
|
EXPECT_TRUE(fe.RegisterMerges());
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
|
EXPECT_THAT(p->error(), Eq("Switch branch from block 30 to case target block "
|
|
"80 escapes the selection construct"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultDirect_IsError) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %80 40 %79 ; continue here
|
|
|
|
%79 = OpLabel ; inner merge
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
|
EXPECT_TRUE(fe.RegisterMerges());
|
|
EXPECT_TRUE(fe.LabelControlFlowConstructs());
|
|
EXPECT_FALSE(fe.FindSwitchCaseHeaders());
|
|
EXPECT_THAT(p->error(), Eq("Switch branch from block 30 to default block 80 "
|
|
"escapes the selection construct"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Conditional) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %40 79 %79
|
|
|
|
%40 = OpLabel
|
|
OpBranchConditional %cond2 %80 %79
|
|
|
|
%79 = OpLabel ; inner merge
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe)) << p->error();
|
|
|
|
auto* bi = fe.GetBlockInfo(40);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(80), 1u);
|
|
EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
ClassifyCFGEdges_LoopContinue_FromNestedSwitchDefaultBody_Unconditional) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %40 79 %79
|
|
|
|
%40 = OpLabel
|
|
OpBranch %80
|
|
|
|
%79 = OpLabel ; inner merge
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(40);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(80), 1u);
|
|
EXPECT_EQ(bi->succ_edge[80], EdgeKind::kLoopContinue);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_LoopContinue_FromNestedLoopHeader_IsError) {
|
|
// Inner loop header tries to do continue to outer loop continue target.
|
|
// This is disallowed by the rule:
|
|
// "a continue block is valid only for the innermost loop it is nested
|
|
// inside of"
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel ; inner loop.
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %59 %50 None
|
|
OpBranchConditional %cond %59 %80 ; break and outer continue
|
|
|
|
%50 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %30 ; inner backedge
|
|
|
|
%59 = OpLabel ; inner merge
|
|
OpStore %var %uint_3
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; outer continue
|
|
OpStore %var %uint_4
|
|
OpBranch %20 ; outer backedge
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 30 to block 80 is an invalid exit from construct "
|
|
"starting at block 30; branch bypasses merge block 59"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_Fallthrough_CaseTailToCase) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 40 %40
|
|
|
|
%20 = OpLabel ; case 20
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40 ; fallthrough
|
|
|
|
%40 = OpLabel ; case 40
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(40), 1u);
|
|
EXPECT_EQ(bi->succ_edge[40], EdgeKind::kCaseFallThrough);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_Fallthrough_CaseTailToDefaultNotMerge) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %40 20 %20
|
|
|
|
%20 = OpLabel ; case 20
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40 ; fallthrough
|
|
|
|
%40 = OpLabel ; case 40
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(40), 1u);
|
|
EXPECT_EQ(bi->succ_edge[40], EdgeKind::kCaseFallThrough);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_Fallthrough_DefaultToCase) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %20 40 %40
|
|
|
|
%20 = OpLabel ; default
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40 ; fallthrough
|
|
|
|
%40 = OpLabel ; case 40
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(40), 1u);
|
|
EXPECT_EQ(bi->succ_edge[40], EdgeKind::kCaseFallThrough);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_Fallthrough_BranchConditionalWith_IfBreak_IsError) {
|
|
// Code generation assumes OpBranchConditional can't have kCaseFallThrough
|
|
// with kIfBreak.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None ; Set up if-break to %99
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %80 None ; switch-selection
|
|
OpSwitch %selector %80 30 %30 40 %40
|
|
|
|
%30 = OpLabel ; first case
|
|
; branch to %99 would be an if-break, but it bypasess the switch merge
|
|
; Also has case fall-through
|
|
OpBranchConditional %cond2 %99 %40
|
|
|
|
%40 = OpLabel ; second case
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; switch-selection's merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; if-selection's merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 30 to block 99 is an invalid exit from "
|
|
"construct starting at block 20; branch bypasses merge block 80"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Forward_IsError) {
|
|
// Code generation assumes OpBranchConditional can't have kCaseFallThrough
|
|
// with kForward.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None ; switch-selection
|
|
OpSwitch %selector %99 20 %20 30 %30
|
|
|
|
; Try to make branch to 35 a kForward branch
|
|
%20 = OpLabel ; first case
|
|
OpBranchConditional %cond2 %25 %30
|
|
|
|
%25 = OpLabel
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel ; second case
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; if-selection's merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Control flow diverges at block 20 (to 25, 30) but it is not "
|
|
"a structured header (it has no merge instruction)"));
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnOutside_IsError) { // NOLINT
|
|
// Code generation assumes OpBranchConditional can't have kCaseFallThrough
|
|
// with kBack.
|
|
//
|
|
// This test has the loop on the outside. The backedge coming from a case
|
|
// clause means the switch is inside the continue construct, and the nesting
|
|
// of the switch's merge means the backedge is coming from a block that is not
|
|
// at the end of the continue construct.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %30 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel ; continue target and
|
|
OpSelectionMerge %80 None ; switch-selection
|
|
OpSwitch %selector %80 40 %40 50 %50
|
|
|
|
; try to make a back edge with a fallthrough
|
|
%40 = OpLabel ; first case
|
|
OpBranchConditional %cond2 %20 %50
|
|
|
|
%50 = OpLabel ; second case
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; switch merge
|
|
OpBranch %20 ; also backedge
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Invalid exit (40->20) from continue construct: 40 is not the "
|
|
"last block in the continue construct starting at 30 "
|
|
"(violates post-dominance rule)"));
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
FindSwitchCaseSelectionHeaders_Fallthrough_BranchConditionalWith_Back_LoopOnInside_FallthroughIsMerge_IsError) { // NOLINT
|
|
// Code generation assumes OpBranchConditional can't have kCaseFallThrough
|
|
// with kBack.
|
|
//
|
|
// This test has the loop on the inside. The merge block is also the
|
|
// fallthrough target.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel ; continue target and
|
|
OpSelectionMerge %99 None ; switch-selection
|
|
OpSwitch %selector %99 20 %20 50 %50
|
|
|
|
%20 = OpLabel ; first case, and loop header
|
|
OpLoopMerge %50 %40 None
|
|
OpBranch %40
|
|
|
|
; try to make a back edge with a fallthrough
|
|
%40 = OpLabel
|
|
OpBranchConditional %cond2 %20 %50
|
|
|
|
%50 = OpLabel ; second case. also the loop merge ; header must dominate its merge block !
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; switch merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 50, 99));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Block 50 is a case block for switch-selection header 10 and "
|
|
"also the merge block for 20 (violates dominance rule)"));
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnInside_FallthroughIsNotMerge_IsError) { // NOLINT
|
|
// Code generation assumes OpBranchConditional can't have kCaseFallThrough
|
|
// with kBack.
|
|
//
|
|
// This test has the loop on the inside. The merge block is not the merge
|
|
// target But the block order gets messed up because of the weird
|
|
// connectivity.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel ; continue target and
|
|
OpSelectionMerge %99 None ; switch-selection
|
|
OpSwitch %selector %99 20 %20 50 %50
|
|
|
|
%20 = OpLabel ; first case, and loop header
|
|
OpLoopMerge %45 %40 None ; move the merge to an unreachable block
|
|
OpBranch %40
|
|
|
|
; try to make a back edge with a fallthrough
|
|
%40 = OpLabel
|
|
OpBranchConditional %cond2 %20 %50
|
|
|
|
%45 = OpLabel ; merge for the loop
|
|
OpUnreachable
|
|
|
|
%50 = OpLabel ; second case. target of fallthrough
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; switch merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(), Eq("Branch from 10 to 50 bypasses continue target 40 "
|
|
"(dominance rule violated)"));
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Back_LoopOnInside_NestedMerge_IsError) { // NOLINT
|
|
// Code generation assumes OpBranchConditional can't have kCaseFallThrough
|
|
// with kBack.
|
|
//
|
|
// This test has the loop on the inside. The fallthrough is an invalid exit
|
|
// from the loop. However, the block order gets all messed up because going
|
|
// from 40 to 50 ends up pulling in 99
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel ; continue target and
|
|
OpSelectionMerge %99 None ; switch-selection
|
|
OpSwitch %selector %99 20 %20 50 %50
|
|
|
|
%20 = OpLabel ; first case, and loop header
|
|
OpLoopMerge %49 %40 None
|
|
OpBranch %40
|
|
|
|
; try to make a back edge with a fallthrough
|
|
%40 = OpLabel
|
|
OpBranchConditional %cond2 %20 %50
|
|
|
|
%49 = OpLabel ; loop merge
|
|
OpBranch %99
|
|
|
|
%50 = OpLabel ; second case
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; switch merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 50, 49, 99));
|
|
EXPECT_THAT(p->error(), Eq("Branch from 10 to 50 bypasses continue target 40 "
|
|
"(dominance rule violated)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_Fallthrough_CaseNonTailToCase_TrueBranch) {
|
|
// This is an unusual one, and is an error. Structurally it looks like this:
|
|
// switch (val) {
|
|
// case 0: {
|
|
// if (cond) {
|
|
// fallthrough;
|
|
// }
|
|
// something = 1;
|
|
// }
|
|
// case 1: { }
|
|
// }
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 50 %50
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond %30 %49
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50 ; attempt to fallthrough
|
|
|
|
%49 = OpLabel
|
|
OpBranch %99
|
|
|
|
%50 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_Fallthrough_CaseNonTailToCase_FalseBranch) {
|
|
// Like previous test, but taking the false branch.
|
|
|
|
// This is an unusual one, and is an error. Structurally it looks like this:
|
|
// switch (val) {
|
|
// case 0: {
|
|
// if (cond) {
|
|
// fallthrough;
|
|
// }
|
|
// something = 1;
|
|
// }
|
|
// case 1: { }
|
|
// }
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 50 %50
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond %49 %30 ;; this is the difference
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50 ; attempt to fallthrough
|
|
|
|
%49 = OpLabel
|
|
OpBranch %99
|
|
|
|
%50 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_Forward_IfToThen) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(20), 1u);
|
|
EXPECT_EQ(bi->succ_edge[20], EdgeKind::kForward);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_Forward_IfToElse) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %99 %30
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(30), 1u);
|
|
EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_Forward_SwitchToCase) {
|
|
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
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(20), 1u);
|
|
EXPECT_EQ(bi->succ_edge[20], EdgeKind::kForward);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_Forward_SwitchToDefaultNotMerge) {
|
|
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
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(30), 1u);
|
|
EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_Forward_LoopHeadToBody) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(30), 1u);
|
|
EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_DomViolation_BeforeIfToSelectionInterior) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %50 ;%50 is a bad branch
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %89 None
|
|
OpBranchConditional %cond %50 %89
|
|
|
|
%50 = OpLabel
|
|
OpBranch %89
|
|
|
|
%89 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_DomViolation_BeforeSwitchToSelectionInterior) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %50 ;%50 is a bad branch
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %89 None
|
|
OpSwitch %selector %89 50 %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %89
|
|
|
|
%89 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_DomViolation_BeforeLoopToLoopBodyInterior) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %50 ;%50 is a bad branch
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %89 %80 None
|
|
OpBranchConditional %cond %50 %89
|
|
|
|
%50 = OpLabel
|
|
OpBranch %89
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20
|
|
|
|
%89 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
// Weird error, but still we caught it.
|
|
// Preferred: Eq("Branch from 10 to 50 bypasses header 20
|
|
// (dominance rule violated)"))
|
|
Eq("Branch from 10 to 50 bypasses continue target 80 (dominance "
|
|
"rule violated)"))
|
|
<< Dump(fe.block_order());
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_DomViolation_BeforeContinueToContinueInterior) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %60
|
|
|
|
%50 = OpLabel ; continue target
|
|
OpBranch %60
|
|
|
|
%60 = OpLabel
|
|
OpBranch %20
|
|
|
|
%89 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 30 to block 60 is an invalid exit from "
|
|
"construct starting at block 20; branch bypasses continue target 50"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
ClassifyCFGEdges_DomViolation_AfterContinueToContinueInterior) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %80 %50 None
|
|
OpBranchConditional %cond %30 %80
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %60
|
|
|
|
%60 = OpLabel
|
|
OpBranch %20
|
|
|
|
%80 = OpLabel
|
|
OpBranch %60 ; bad branch
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 50 to block 60 is an invalid exit from "
|
|
"construct starting at block 50; branch bypasses merge block 80"));
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
FindSwitchCaseHeaders_DomViolation_SwitchCase_CantBeMergeForOtherConstruct) { // NOLINT
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 50 %50
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond %30 %50
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; case and merge block. Error
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Block 50 is a case block for switch-selection header 10 and "
|
|
"also the merge block for 20 (violates dominance rule)"));
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
ClassifyCFGEdges_DomViolation_SwitchDefault_CantBeMergeForOtherConstruct) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %50 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond %30 %50
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; default-case and merge block. Error
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowFindSwitchCaseHeaders(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Block 50 is the default block for switch-selection header 10 "
|
|
"and also the merge block for 20 (violates dominance rule)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_TooManyBackedges) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranchConditional %cond2 %20 %50
|
|
|
|
%50 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Invalid backedge (30->20): 30 is not in a continue construct"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_NeededMerge_BranchConditional) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%20 = OpLabel
|
|
OpBranchConditional %cond %30 %40
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Control flow diverges at block 20 (to 30, 40) but it is not "
|
|
"a structured header (it has no merge instruction)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_NeededMerge_Switch) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSwitch %selector %99 20 %20 30 %30
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Control flow diverges at block 10 (to 99, 20) but it is not "
|
|
"a structured header (it has no merge instruction)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_Pathological_Forward_LoopHeadSplitBody) {
|
|
// In this case the branch-conditional in the loop header is really also a
|
|
// selection header.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %50 ; what to make of this?
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%50 = OpLabel
|
|
OpBranch %99
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->succ_edge.count(30), 1u);
|
|
EXPECT_EQ(bi->succ_edge[30], EdgeKind::kForward);
|
|
EXPECT_EQ(bi->succ_edge.count(50), 1u);
|
|
EXPECT_EQ(bi->succ_edge[50], EdgeKind::kForward);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_Pathological_Forward_Premerge) {
|
|
// Two arms of an if-selection converge early, before the merge block
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %30
|
|
|
|
%20 = OpLabel
|
|
OpBranch %50
|
|
|
|
%30 = OpLabel
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; this is an early merge!
|
|
OpBranch %60
|
|
|
|
%60 = OpLabel ; still early merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi20 = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi20, nullptr);
|
|
EXPECT_EQ(bi20->succ_edge.count(50), 1u);
|
|
EXPECT_EQ(bi20->succ_edge[50], EdgeKind::kForward);
|
|
|
|
auto* bi30 = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi30, nullptr);
|
|
EXPECT_EQ(bi30->succ_edge.count(50), 1u);
|
|
EXPECT_EQ(bi30->succ_edge[50], EdgeKind::kForward);
|
|
|
|
auto* bi50 = fe.GetBlockInfo(50);
|
|
ASSERT_NE(bi50, nullptr);
|
|
EXPECT_EQ(bi50->succ_edge.count(60), 1u);
|
|
EXPECT_EQ(bi50->succ_edge[60], EdgeKind::kForward);
|
|
|
|
auto* bi60 = fe.GetBlockInfo(60);
|
|
ASSERT_NE(bi60, nullptr);
|
|
EXPECT_EQ(bi60->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi60->succ_edge[99], EdgeKind::kIfBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_Pathological_Forward_Regardless) {
|
|
// Both arms of an OpBranchConditional go to the same target.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %20 ; same target!
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi10 = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi10, nullptr);
|
|
EXPECT_EQ(bi10->succ_edge.count(20), 1u);
|
|
EXPECT_EQ(bi10->succ_edge[20], EdgeKind::kForward);
|
|
|
|
auto* bi20 = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi20, nullptr);
|
|
EXPECT_EQ(bi20->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_NoIf) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
|
|
auto* bi = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi, nullptr);
|
|
EXPECT_EQ(bi->true_head, 0u);
|
|
EXPECT_EQ(bi->false_head, 0u);
|
|
EXPECT_EQ(bi->premerge_head, 0u);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_ThenElse) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %30
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
|
|
auto* bi10 = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi10, nullptr);
|
|
EXPECT_EQ(bi10->true_head, 20u);
|
|
EXPECT_EQ(bi10->false_head, 30u);
|
|
EXPECT_EQ(bi10->premerge_head, 0u);
|
|
|
|
auto* bi20 = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi20, nullptr);
|
|
EXPECT_EQ(bi20->true_head, 0u);
|
|
EXPECT_EQ(bi20->false_head, 0u);
|
|
EXPECT_EQ(bi20->premerge_head, 0u);
|
|
|
|
auto* bi30 = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi30, nullptr);
|
|
EXPECT_EQ(bi30->true_head, 0u);
|
|
EXPECT_EQ(bi30->false_head, 0u);
|
|
EXPECT_EQ(bi30->premerge_head, 0u);
|
|
|
|
auto* bi99 = fe.GetBlockInfo(99);
|
|
ASSERT_NE(bi99, nullptr);
|
|
EXPECT_EQ(bi99->true_head, 0u);
|
|
EXPECT_EQ(bi99->false_head, 0u);
|
|
EXPECT_EQ(bi99->premerge_head, 0u);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_IfOnly) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
|
|
auto* bi10 = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi10, nullptr);
|
|
EXPECT_EQ(bi10->true_head, 30u);
|
|
EXPECT_EQ(bi10->false_head, 0u);
|
|
EXPECT_EQ(bi10->premerge_head, 0u);
|
|
|
|
auto* bi30 = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi30, nullptr);
|
|
EXPECT_EQ(bi30->true_head, 0u);
|
|
EXPECT_EQ(bi30->false_head, 0u);
|
|
EXPECT_EQ(bi30->premerge_head, 0u);
|
|
|
|
auto* bi99 = fe.GetBlockInfo(99);
|
|
ASSERT_NE(bi99, nullptr);
|
|
EXPECT_EQ(bi99->true_head, 0u);
|
|
EXPECT_EQ(bi99->false_head, 0u);
|
|
EXPECT_EQ(bi99->premerge_head, 0u);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_ElseOnly) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %99 %30
|
|
|
|
%30 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
|
|
auto* bi10 = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi10, nullptr);
|
|
EXPECT_EQ(bi10->true_head, 0u);
|
|
EXPECT_EQ(bi10->false_head, 30u);
|
|
EXPECT_EQ(bi10->premerge_head, 0u);
|
|
|
|
auto* bi30 = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi30, nullptr);
|
|
EXPECT_EQ(bi30->true_head, 0u);
|
|
EXPECT_EQ(bi30->false_head, 0u);
|
|
EXPECT_EQ(bi30->premerge_head, 0u);
|
|
|
|
auto* bi99 = fe.GetBlockInfo(99);
|
|
ASSERT_NE(bi99, nullptr);
|
|
EXPECT_EQ(bi99->true_head, 0u);
|
|
EXPECT_EQ(bi99->false_head, 0u);
|
|
EXPECT_EQ(bi99->premerge_head, 0u);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_Regardless) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %20 ; same target
|
|
|
|
%20 = OpLabel
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 99));
|
|
|
|
auto* bi10 = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi10, nullptr);
|
|
EXPECT_EQ(bi10->true_head, 20u);
|
|
EXPECT_EQ(bi10->false_head, 20u);
|
|
EXPECT_EQ(bi10->premerge_head, 0u);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_Premerge_Simple) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %30
|
|
|
|
%20 = OpLabel
|
|
OpBranch %80
|
|
|
|
%30 = OpLabel
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; premerge node
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
|
|
|
|
auto* bi10 = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi10, nullptr);
|
|
EXPECT_EQ(bi10->true_head, 20u);
|
|
EXPECT_EQ(bi10->false_head, 30u);
|
|
EXPECT_EQ(bi10->premerge_head, 80u);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
FindIfSelectionInternalHeaders_Premerge_ThenDirectToElse) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %30
|
|
|
|
%20 = OpLabel
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; premerge node
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
|
|
|
|
auto* bi10 = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi10, nullptr);
|
|
EXPECT_EQ(bi10->true_head, 20u);
|
|
EXPECT_EQ(bi10->false_head, 30u);
|
|
EXPECT_EQ(bi10->premerge_head, 30u);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
FindIfSelectionInternalHeaders_Premerge_ElseDirectToThen) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %30
|
|
|
|
%20 = OpLabel
|
|
OpBranch %80 ; branches to premerge
|
|
|
|
%30 = OpLabel ; else
|
|
OpBranch %20 ; branches to then
|
|
|
|
%80 = OpLabel ; premerge node
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 80, 99));
|
|
|
|
auto* bi10 = fe.GetBlockInfo(10);
|
|
ASSERT_NE(bi10, nullptr);
|
|
EXPECT_EQ(bi10->true_head, 20u);
|
|
EXPECT_EQ(bi10->false_head, 30u);
|
|
EXPECT_EQ(bi10->premerge_head, 20u);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
FindIfSelectionInternalHeaders_Premerge_MultiCandidate_IsError) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %30
|
|
|
|
%20 = OpLabel
|
|
; Try to force several branches down into "else" territory,
|
|
; but we error out earlier in the flow due to lack of merge
|
|
; instruction.
|
|
OpBranchConditional %cond2 %70 %80
|
|
|
|
%30 = OpLabel
|
|
OpBranch %70
|
|
|
|
%70 = OpLabel ; candidate premerge
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; canddiate premerge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
// Error out sooner in the flow
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Control flow diverges at block 20 (to 70, 80) but it is not "
|
|
"a structured header (it has no merge instruction)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromThen_ForwardWithinThen) {
|
|
// SPIR-V allows this unusual configuration.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpBranchConditional %cond2 %99 %80 ; break with forward edge
|
|
|
|
%80 = OpLabel ; still in then clause
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 99));
|
|
|
|
auto* bi20 = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi20, nullptr);
|
|
EXPECT_EQ(bi20->succ_edge.count(80), 1u);
|
|
EXPECT_EQ(bi20->succ_edge[80], EdgeKind::kForward);
|
|
EXPECT_EQ(bi20->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
|
|
|
|
EXPECT_THAT(p->error(), Eq(""));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_FromElse_ForwardWithinElse) {
|
|
// SPIR-V allows this unusual configuration.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %30
|
|
|
|
%20 = OpLabel
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel ; else clause
|
|
OpBranchConditional %cond2 %99 %80 ; break with forward edge
|
|
|
|
%80 = OpLabel ; still in then clause
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
|
|
|
|
auto* bi30 = fe.GetBlockInfo(30);
|
|
ASSERT_NE(bi30, nullptr);
|
|
EXPECT_EQ(bi30->succ_edge.count(80), 1u);
|
|
EXPECT_EQ(bi30->succ_edge[80], EdgeKind::kForward);
|
|
EXPECT_EQ(bi30->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi30->succ_edge[99], EdgeKind::kIfBreak);
|
|
|
|
EXPECT_THAT(p->error(), Eq(""));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_WithForwardToPremerge) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %30
|
|
|
|
%20 = OpLabel ; then
|
|
OpBranchConditional %cond2 %99 %80 ; break with forward to premerge
|
|
|
|
%30 = OpLabel ; else
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; premerge node
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 80, 99));
|
|
|
|
auto* bi20 = fe.GetBlockInfo(20);
|
|
ASSERT_NE(bi20, nullptr);
|
|
EXPECT_EQ(bi20->succ_edge.count(80), 1u);
|
|
EXPECT_EQ(bi20->succ_edge[80], EdgeKind::kForward);
|
|
EXPECT_EQ(bi20->succ_edge.count(99), 1u);
|
|
EXPECT_EQ(bi20->succ_edge[99], EdgeKind::kIfBreak);
|
|
|
|
EXPECT_THAT(p->error(), Eq(""));
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeTrueHeader) { // NOLINT - line length
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %40 %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond2 %30 %40
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; inner merge, and true-head for outer if-selection
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Block 40 is the true branch for if-selection header 10 and also the "
|
|
"merge block for header block 20 (violates dominance rule)"));
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeFalseHeader) { // NOLINT - line length
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %40
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %30 %40
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; inner merge, and true-head for outer if-selection
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Block 40 is the false branch for if-selection header 10 and also the "
|
|
"merge block for header block 20 (violates dominance rule)"));
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBePremerge) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel ; outer if-header
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %50
|
|
|
|
%20 = OpLabel
|
|
OpBranch %70
|
|
|
|
%50 = OpLabel ; inner if-header
|
|
OpSelectionMerge %70 None
|
|
OpBranchConditional %cond %60 %70
|
|
|
|
%60 = OpLabel
|
|
OpBranch %70
|
|
|
|
%70 = OpLabel ; inner merge, and premerge for outer if-selection
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; outer merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Block 70 is the merge block for 50 but has alternate paths "
|
|
"reaching it, starting from blocks 20 and 50 which are the "
|
|
"true and false branches for the if-selection header block 10 "
|
|
"(violates dominance rule)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%5 = OpLabel
|
|
OpBranch %10
|
|
|
|
%10 = OpLabel
|
|
OpLoopMerge %99 %90 None
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %99 %30 ; true branch breaking is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; selection merge
|
|
OpBranch %90
|
|
|
|
%90 = OpLabel ; continue target
|
|
OpBranch %10 ; backedge
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
EXPECT_THAT(p->error(), Eq(""));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%5 = OpLabel
|
|
OpBranch %10
|
|
|
|
%10 = OpLabel
|
|
OpLoopMerge %99 %90 None
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %90 %30 ; true branch continue is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; selection merge
|
|
OpBranch %90
|
|
|
|
%90 = OpLabel ; continue target
|
|
OpBranch %10 ; backedge
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
EXPECT_THAT(p->error(), Eq(""));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
FindIfSelectionInternalHeaders_TrueBranch_SwitchBreak_Ok) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %uint_20 %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %99 %30 ; true branch switch break is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; if-selection merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; switch merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
EXPECT_THAT(p->error(), Eq(""));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_FalseBranch_LoopBreak_Ok) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%5 = OpLabel
|
|
OpBranch %10
|
|
|
|
%10 = OpLabel
|
|
OpLoopMerge %99 %90 None
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %30 %99 ; false branch breaking is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; selection merge
|
|
OpBranch %90
|
|
|
|
%90 = OpLabel ; continue target
|
|
OpBranch %10 ; backedge
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
EXPECT_THAT(p->error(), Eq(""));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%5 = OpLabel
|
|
OpBranch %10
|
|
|
|
%10 = OpLabel
|
|
OpLoopMerge %99 %90 None
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %30 %90 ; false branch continue is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; selection merge
|
|
OpBranch %90
|
|
|
|
%90 = OpLabel ; continue target
|
|
OpBranch %10 ; backedge
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
EXPECT_THAT(p->error(), Eq(""));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
FindIfSelectionInternalHeaders_FalseBranch_SwitchBreak_Ok) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %uint_20 %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %30 %99 ; false branch switch break is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; if-selection merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; switch merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
|
EXPECT_THAT(p->error(), Eq(""));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_IfBreak_FromThen_ForwardWithinThen) {
|
|
// Exercises the hard case where we a single OpBranchConditional has both
|
|
// IfBreak and Forward edges, within the true-branch clause.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %50
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranchConditional %cond2 %99 %30 ; kIfBreak with kForward
|
|
|
|
%30 = OpLabel ; still in then clause
|
|
OpStore %var %uint_3
|
|
OpBranch %99
|
|
|
|
%50 = OpLabel ; else clause
|
|
OpStore %var %uint_4
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
VariableDeclStatement{
|
|
Variable{
|
|
guard10
|
|
function
|
|
__bool
|
|
{
|
|
ScalarConstructor[not set]{true}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{guard10}
|
|
ScalarConstructor[not set]{false}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
Identifier[not set]{guard10}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{guard10}
|
|
ScalarConstructor[not set]{false}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
If{
|
|
(
|
|
Identifier[not set]{guard10}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{guard10}
|
|
ScalarConstructor[not set]{false}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_IfBreak_FromElse_ForwardWithinElse) {
|
|
// Exercises the hard case where we a single OpBranchConditional has both
|
|
// IfBreak and Forward edges, within the false-branch clause.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %50
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %99
|
|
|
|
%50 = OpLabel ; else clause
|
|
OpStore %var %uint_3
|
|
OpBranchConditional %cond2 %99 %80 ; kIfBreak with kForward
|
|
|
|
%80 = OpLabel ; still in then clause
|
|
OpStore %var %uint_4
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
VariableDeclStatement{
|
|
Variable{
|
|
guard10
|
|
function
|
|
__bool
|
|
{
|
|
ScalarConstructor[not set]{true}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{guard10}
|
|
ScalarConstructor[not set]{false}
|
|
}
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
If{
|
|
(
|
|
Identifier[not set]{guard10}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{guard10}
|
|
ScalarConstructor[not set]{false}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
Identifier[not set]{guard10}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{guard10}
|
|
ScalarConstructor[not set]{false}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_IfBreak_FromThenWithForward_FromElseWithForward_AlsoPremerge) {
|
|
// This is a combination of the previous two, but also adding a premerge.
|
|
// We have IfBreak and Forward edges from the same OpBranchConditional, and
|
|
// this occurs in the true-branch clause, the false-branch clause, and within
|
|
// the premerge clause. Flow guards have to be sprinkled in lots of places.
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %50
|
|
|
|
%20 = OpLabel ; then
|
|
OpStore %var %uint_2
|
|
OpBranchConditional %cond2 %21 %99 ; kForward and kIfBreak
|
|
|
|
%21 = OpLabel ; still in then clause
|
|
OpStore %var %uint_3
|
|
OpBranch %80 ; to premerge
|
|
|
|
%50 = OpLabel ; else clause
|
|
OpStore %var %uint_4
|
|
OpBranchConditional %cond2 %99 %51 ; kIfBreak with kForward
|
|
|
|
%51 = OpLabel ; still in else clause
|
|
OpStore %var %uint_5
|
|
OpBranch %80 ; to premerge
|
|
|
|
%80 = OpLabel ; premerge
|
|
OpStore %var %uint_6
|
|
OpBranchConditional %cond3 %81 %99
|
|
|
|
%81 = OpLabel ; premerge
|
|
OpStore %var %uint_7
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_8
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error() << assembly;
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
VariableDeclStatement{
|
|
Variable{
|
|
guard10
|
|
function
|
|
__bool
|
|
{
|
|
ScalarConstructor[not set]{true}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{guard10}
|
|
ScalarConstructor[not set]{false}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
Identifier[not set]{guard10}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
If{
|
|
(
|
|
Identifier[not set]{guard10}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{guard10}
|
|
ScalarConstructor[not set]{false}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
Identifier[not set]{guard10}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
Identifier[not set]{guard10}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{6u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{guard10}
|
|
ScalarConstructor[not set]{false}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
Identifier[not set]{guard10}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{guard10}
|
|
ScalarConstructor[not set]{false}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{8u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, BlockIsContinueForMoreThanOneHeader) {
|
|
// This is disallowed by the rule:
|
|
// "a continue block is valid only for the innermost loop it is nested
|
|
// inside of"
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel ; outer loop
|
|
OpLoopMerge %99 %50 None
|
|
OpBranchConditional %cond %50 %99
|
|
|
|
%50 = OpLabel ; continue target, but also single-block loop
|
|
OpLoopMerge %80 %50 None
|
|
OpBranchConditional %cond2 %50 %80
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20 ; backedge for outer loop
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
fe.RegisterBasicBlocks();
|
|
fe.ComputeBlockOrderAndPositions();
|
|
EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder());
|
|
EXPECT_FALSE(fe.RegisterMerges());
|
|
EXPECT_THAT(p->error(), Eq("Block 50 declared as continue target for more "
|
|
"than one header: 20, 50"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_If_Empty) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %99 %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_If_Then_NoElse) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_If_NoThen_Else) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %99 %30
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_If_Then_Else) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %30 %40
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %99
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_If_Then_Else_Premerge) {
|
|
// TODO(dneto): This should get an extra if(true) around
|
|
// the premerge code.
|
|
// See https://bugs.chromium.org/p/tint/issues/detail?id=82
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %30 %40
|
|
|
|
%80 = OpLabel ; premerge
|
|
OpStore %var %uint_3
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %80
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %80
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_If_Then_Premerge) {
|
|
// The premerge *is* the else.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %30 %80
|
|
|
|
%80 = OpLabel ; premerge
|
|
OpStore %var %uint_3
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %80
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_If_Else_Premerge) {
|
|
// The premerge *is* the then-clause.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %80 %30
|
|
|
|
%80 = OpLabel ; premerge
|
|
OpStore %var %uint_3
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %80
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_If_Nest_If) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %30 %40
|
|
|
|
%30 = OpLabel ;; inner if #1
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %39 None
|
|
OpBranchConditional %cond2 %33 %39
|
|
|
|
%33 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %39
|
|
|
|
%39 = OpLabel ;; inner merge
|
|
OpStore %var %uint_3
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel ;; inner if #2
|
|
OpStore %var %uint_4
|
|
OpSelectionMerge %49 None
|
|
OpBranchConditional %cond2 %49 %43
|
|
|
|
%43 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpBranch %49
|
|
|
|
%49 = OpLabel ;; 2nd inner merge
|
|
OpStore %var %uint_6
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{6u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_TrueBackedge) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_FalseBackedge) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %99 %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_BothBackedge) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %20 %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_SingleBlock_UnconditionalBackege) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %20 None
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_Unconditional_Body_SingleBlockContinue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %50 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_Unconditional_Body_MultiBlockContinue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %50 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranch %60
|
|
|
|
%60 = OpLabel
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_Unconditional_Body_ContinueNestIf) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %50 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %50
|
|
|
|
%50 = OpLabel ; continue target; also if-header
|
|
OpStore %var %uint_3
|
|
OpSelectionMerge %80 None
|
|
OpBranchConditional %cond2 %60 %80
|
|
|
|
%60 = OpLabel
|
|
OpStore %var %uint_4
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %999
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{999u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_MultiBlockContinueIsEntireLoop) {
|
|
// Test case where both branches exit. e.g both go to merge.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel ; its own continue target
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %20 None
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranchConditional %cond %99 %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_Never) {
|
|
// Test case where both branches exit. e.g both go to merge.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %99 %99
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_2
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Break{}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_HeaderBreakAndContinue) {
|
|
// Header block branches to merge, and to an outer continue.
|
|
// This is disallowed by the rule:
|
|
// "a continue block is valid only for the innermost loop it is nested
|
|
// inside of"
|
|
// See test ClassifyCFGEdges_LoopContinue_FromNestedLoopHeader_IsError
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_TrueToBody_FalseBreaks) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_3
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_4
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_FalseToBody_TrueBreaks) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_3
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_4
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_NestedIfContinue) {
|
|
// By construction, it has to come from nested code.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond %40 %50
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %80 ; continue edge
|
|
|
|
%50 = OpLabel ; inner selection merge
|
|
OpStore %var %uint_2
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_3
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Continue{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_BodyAlwaysBreaks) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %99 ; break is here
|
|
|
|
%80 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %20 ; backedge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Break{}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromTrue) {
|
|
// The else-branch has a continue but it's skipped because it's from a
|
|
// block that immediately precedes the continue construct.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranchConditional %cond %99 %80
|
|
|
|
%80 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %20 ; backedge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromFalse) {
|
|
// The else-branch has a continue but it's skipped because it's from a
|
|
// block that immediately precedes the continue construct.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranchConditional %cond %80 %99
|
|
|
|
%80 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %20 ; backedge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromTrue_Early) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranchConditional %cond %99 %70
|
|
|
|
%70 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %20 ; backedge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Loop_BodyConditionallyBreaks_FromFalse_Early) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranchConditional %cond %70 %99
|
|
|
|
%70 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %20 ; backedge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsMerge_NoCases) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
// First do no special control flow: no fallthroughs, breaks, continues.
|
|
TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsMerge_OneCase) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsMerge_TwoCases) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 30 %30
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 30u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsMerge_CasesWithDup) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 30 %30 40 %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 30u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
Case 20u, 40u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsCase_NoDupCases) {
|
|
// The default block is not the merge block. But not the same as a case
|
|
// either.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %30 20 %20 40 %40
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel ; the named default block
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 40u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{40u}
|
|
}
|
|
}
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
}
|
|
Default{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsCase_WithDupCase) {
|
|
// The default block is not the merge block and is the same as a case.
|
|
// We emit the default case separately, but just before the labeled
|
|
// case, and with a fallthrough.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %30 20 %20 30 %30 40 %40
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel ; the named default block, also a case
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 40u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{40u}
|
|
}
|
|
}
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
}
|
|
Default{
|
|
Fallthrough{}
|
|
}
|
|
Case 30u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Switch_Case_SintValue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
; SPIR-V assembler doesn't support negative literals in switch
|
|
OpSwitch %signed_selector %99 20 %20 2000000000 %30 !4000000000 %40
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42}
|
|
{
|
|
Case -294967296{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{40u}
|
|
}
|
|
}
|
|
Case 2000000000{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
Case 20{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Switch_Case_UintValue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 2000000000 %30 50 %40
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranch %99
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 50u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{40u}
|
|
}
|
|
}
|
|
Case 2000000000u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Return_TopLevel) {
|
|
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.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Return_InsideIf) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpReturn
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Return{}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Return_InsideLoop) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %30
|
|
|
|
%30 = OpLabel
|
|
OpReturn
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Return{}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_ReturnValue_TopLevel) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%200 = OpFunction %uint None %uintfn
|
|
|
|
%210 = OpLabel
|
|
OpReturnValue %uint_2
|
|
|
|
OpFunctionEnd
|
|
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
%11 = OpFunctionCall %uint %200
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Return{
|
|
{
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_ReturnValue_InsideIf) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%200 = OpFunction %uint None %uintfn
|
|
|
|
%210 = OpLabel
|
|
OpSelectionMerge %299 None
|
|
OpBranchConditional %cond %220 %299
|
|
|
|
%220 = OpLabel
|
|
OpReturnValue %uint_2
|
|
|
|
%299 = OpLabel
|
|
OpReturnValue %uint_3
|
|
|
|
OpFunctionEnd
|
|
|
|
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
%11 = OpFunctionCall %uint %200
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Return{
|
|
{
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Return{
|
|
{
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_ReturnValue_Loop) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%200 = OpFunction %void None %voidfn
|
|
|
|
%210 = OpLabel
|
|
OpBranch %220
|
|
|
|
%220 = OpLabel
|
|
OpLoopMerge %299 %280 None
|
|
OpBranchConditional %cond %230 %230
|
|
|
|
%230 = OpLabel
|
|
OpReturnValue %uint_2
|
|
|
|
%280 = OpLabel
|
|
OpBranch %220
|
|
|
|
%299 = OpLabel
|
|
OpReturnValue %uint_3
|
|
|
|
OpFunctionEnd
|
|
|
|
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
%11 = OpFunctionCall %uint %200
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Return{
|
|
{
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
Return{
|
|
{
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Kill_TopLevel) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpKill
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Discard{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Kill_InsideIf) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpKill
|
|
|
|
%99 = OpLabel
|
|
OpKill
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Discard{}
|
|
}
|
|
}
|
|
Discard{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Kill_InsideLoop) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %30
|
|
|
|
%30 = OpLabel
|
|
OpKill
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpKill
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Discard{}
|
|
}
|
|
Discard{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Unreachable_TopLevel) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpUnreachable
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Unreachable_InsideIf) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpUnreachable
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Return{}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Unreachable_InsideLoop) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %30 %30
|
|
|
|
%30 = OpLabel
|
|
OpUnreachable
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Return{}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Unreachable_InNonVoidFunction) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%200 = OpFunction %uint None %uintfn
|
|
|
|
%210 = OpLabel
|
|
OpUnreachable
|
|
|
|
OpFunctionEnd
|
|
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
%11 = OpFunctionCall %uint %200
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Return{
|
|
{
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_BackEdge_MultiBlockLoop) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %20 ; here is one
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_BackEdge_SingleBlockLoop) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %20 None
|
|
OpBranch %20 ; backedge in single block loop
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_SwitchBreak_LastInCase) {
|
|
// When the break is last in its case, we omit it because it's implicit in
|
|
// WGSL.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranch %99 ; branch to merge. Last in case
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_SwitchBreak_NotLastInCase) {
|
|
// When the break is not last in its case, we must emit a 'break'
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond %40 %50
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
OpBranch %99 ; branch to merge. Not last in case
|
|
|
|
%50 = OpLabel ; inner merge
|
|
OpStore %var %uint_50
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{40u}
|
|
}
|
|
Break{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{50u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_LoopBreak_MultiBlockLoop_FromBody) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %99 ; break is here
|
|
|
|
%80 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranch %20 ; backedge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Break{}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructConditional) {
|
|
// This case is invalid because the backedge block doesn't post-dominate the
|
|
// continue target.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %30 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel ; continue target; also an if-header
|
|
OpSelectionMerge %80 None
|
|
OpBranchConditional %cond %40 %80
|
|
|
|
%40 = OpLabel
|
|
OpBranch %99 ; break, inside a nested if.
|
|
|
|
%80 = OpLabel
|
|
OpBranch %20 ; backedge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(fe.EmitBody()) << p->error();
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Invalid exit (40->99) from continue construct: 40 is not the "
|
|
"last block in the continue construct starting at 30 "
|
|
"(violates post-dominance rule)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_1
|
|
OpBranch %99 ; should be a backedge
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Break{}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_LoopContinue_LastInLoopConstruct) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %80 ; continue edge from last block before continue target
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_2
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_LoopContinue_BeforeLast) {
|
|
// By construction, it has to come from nested code.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond %40 %50
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %80 ; continue edge
|
|
|
|
%50 = OpLabel ; inner selection merge
|
|
OpStore %var %uint_2
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_3
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Continue{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_LoopContinue_FromSwitch) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %79 40 %40
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_4
|
|
OpBranch %80 ; continue edge
|
|
|
|
%79 = OpLabel ; switch merge
|
|
OpStore %var %uint_5
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_6
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 40u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
Continue{}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{6u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_IfBreak_FromThen) {
|
|
// When unconditional, the if-break must be last in the then clause.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_IfBreak_FromElse) {
|
|
// When unconditional, the if-break must be last in the else clause.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %99 %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_Fallthrough) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 30 %30
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranch %30 ; uncondtional fallthrough
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
Fallthrough{}
|
|
}
|
|
Case 30u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_Branch_Forward) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %99 ; forward
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
// Test matrix for normal OpBranchConditional:
|
|
//
|
|
// kBack with:
|
|
// kBack : TESTED dup general case
|
|
// kSwitchBreak: invalid (invalid escape, or invalid backedge)
|
|
// kLoopBreak: TESTED in single- and multi block loop configurations
|
|
// kLoopContinue: invalid
|
|
// If single block loop, then the continue is backward
|
|
// If continue is forward, then it's a continue from a
|
|
// continue which is also invalid.
|
|
// kIfBreak: invalid: loop and if must have distinct merge blocks
|
|
// kCaseFallThrough: invalid: loop header must dominate its merge
|
|
// kForward: impossible; would be a loop break
|
|
//
|
|
// kSwitchBreak with:
|
|
// kBack : symmetry
|
|
// kSwitchBreak: dup general case
|
|
// kLoopBreak: invalid; only one kind of break allowed
|
|
// kLoopContinue: TESTED
|
|
// kIfBreak: invalid: switch and if must have distinct merge blocks
|
|
// kCaseFallThrough: TESTED
|
|
// kForward: TESTED
|
|
//
|
|
// kLoopBreak with:
|
|
// kBack : symmetry
|
|
// kSwitchBreak: symmetry
|
|
// kLoopBreak: dup general case
|
|
// kLoopContinue: TESTED
|
|
// kIfBreak: invalid: switch and if must have distinct merge blocks
|
|
// kCaseFallThrough: not possible, because switch break conflicts with loop
|
|
// break kForward: TESTED
|
|
//
|
|
// kLoopContinue with:
|
|
// kBack : symmetry
|
|
// kSwitchBreak: symmetry
|
|
// kLoopBreak: symmetry
|
|
// kLoopContinue: dup general case
|
|
// kIfBreak: TESTED
|
|
// kCaseFallThrough: TESTED
|
|
// kForward: TESTED
|
|
//
|
|
// kIfBreak with:
|
|
// kBack : symmetry
|
|
// kSwitchBreak: symmetry
|
|
// kLoopBreak: symmetry
|
|
// kLoopContinue: symmetry
|
|
// kIfBreak: dup general case
|
|
// kCaseFallThrough: invalid; violates nesting or unique merges
|
|
// kForward: invalid: needs a merge instruction
|
|
//
|
|
// kCaseFallThrough with:
|
|
// kBack : symmetry
|
|
// kSwitchBreak: symmetry
|
|
// kLoopBreak: symmetry
|
|
// kLoopContinue: symmetry
|
|
// kIfBreak: symmetry
|
|
// kCaseFallThrough: dup general case
|
|
// kForward: invalid (tested)
|
|
//
|
|
// kForward with:
|
|
// kBack : symmetry
|
|
// kSwitchBreak: symmetry
|
|
// kLoopBreak: symmetry
|
|
// kLoopContinue: symmetry
|
|
// kIfBreak: symmetry
|
|
// kCaseFallThrough: symmetry
|
|
// kForward: dup general case
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Back_SingleBlock_Back) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %20 %20
|
|
|
|
%99 = OpLabel ; dead
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnTrue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %99 %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_Back_SingleBlock_LoopBreak_OnFalse) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %20 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnTrue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpBranchConditional %cond %99 %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
continuing {
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_Back_MultiBlock_LoopBreak_OnFalse) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
continuing {
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_SwitchBreak_SwitchBreak_LastInCase) {
|
|
// When the break is last in its case, we omit it because it's implicit in
|
|
// WGSL.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranchConditional %cond2 %99 %99 ; branch to merge. Last in case
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_SwitchBreak_SwitchBreak_NotLastInCase) {
|
|
// When the break is not last in its case, we must emit a 'break'
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond %40 %50
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
OpBranchConditional %cond2 %99 %99 ; branch to merge. Not last in case
|
|
|
|
%50 = OpLabel ; inner merge
|
|
OpStore %var %uint_50
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{40u}
|
|
}
|
|
Break{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{50u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %79 40 %40
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
OpBranchConditional %cond %80 %79 ; break; continue on true
|
|
|
|
%79 = OpLabel
|
|
OpStore %var %uint_6
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_7
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpStore %var %uint_8
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 40u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{40u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{6u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{8u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %79 40 %40
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
OpBranchConditional %cond %79 %80 ; break; continue on false
|
|
|
|
%79 = OpLabel
|
|
OpStore %var %uint_6
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_7
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpStore %var %uint_8
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 40u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{40u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{6u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{8u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_SwitchBreak_Forward_OnTrue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranchConditional %cond %30 %99 ; break; forward on true
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; switch merge
|
|
OpStore %var %uint_8
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{8u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_SwitchBreak_Forward_OnFalse) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranchConditional %cond %99 %30 ; break; forward on false
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; switch merge
|
|
OpStore %var %uint_8
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{8u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 30 %30
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranchConditional %cond %30 %99; fallthrough on true
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
Fallthrough{}
|
|
}
|
|
Case 30u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 30 %30
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranchConditional %cond %99 %30; fallthrough on false
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
Fallthrough{}
|
|
}
|
|
Case 30u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_LoopBreak_SingleBlock_LoopBreak) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %99 %99
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Break{}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_LoopBreak_MultiBlock_LoopBreak) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranchConditional %cond %99 %99
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Break{}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Continue_OnTrue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %25
|
|
|
|
; Need this extra selection to make another block between
|
|
; %30 and the continue target, so we actually induce a Continue
|
|
; statement to exist.
|
|
%25 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond2 %30 %40
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
; break; continue on true
|
|
OpBranchConditional %cond %80 %99
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Continue_OnFalse) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %25
|
|
|
|
; Need this extra selection to make another block between
|
|
; %30 and the continue target, so we actually induce a Continue
|
|
; statement to exist.
|
|
%25 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond2 %30 %40
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
; break; continue on false
|
|
OpBranchConditional %cond %99 %80
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_LoopBreak_Fallthrough_IsError) {
|
|
// It's an error because switch break conflicts with loop break.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %79 40 %40 50 %50
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
; error: branch to 99 bypasses switch's merge
|
|
OpBranchConditional %cond %99 %50 ; loop break; fall through
|
|
|
|
%50 = OpLabel
|
|
OpStore %var %uint_50
|
|
OpBranch %79
|
|
|
|
%79 = OpLabel ; switch merge
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(fe.EmitBody()) << p->error();
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 40 to block 99 is an invalid exit from construct "
|
|
"starting at block 30; branch bypasses merge block 79"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Forward_OnTrue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
; break; forward on true
|
|
OpBranchConditional %cond %40 %99
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Forward_OnFalse) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
; break; forward on false
|
|
OpBranchConditional %cond %99 %40
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Continue_FromHeader) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranchConditional %cond %80 %80 ; to continue
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_Continue_Continue_AfterHeader_Unconditional) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranchConditional %cond %80 %80 ; to continue
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional) {
|
|
// Create an intervening block so we actually require a "continue" statement
|
|
// instead of just an adjacent fallthrough to the continue target.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond2 %40 %50
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranchConditional %cond3 %80 %80 ; to continue
|
|
|
|
%50 = OpLabel ; merge for selection
|
|
OpStore %var %uint_4
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_5
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_6
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
Continue{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{6u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(
|
|
SpvParserTest,
|
|
EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing) { // NOLINT
|
|
// Like the previous tests, but with an empty continuing clause.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond2 %40 %50
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranchConditional %cond3 %80 %80 ; to continue
|
|
|
|
%50 = OpLabel ; merge for selection
|
|
OpStore %var %uint_4
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
; no statements here.
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_6
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
Continue{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{6u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopContinue_FromSwitch) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %79 40 %40
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_4
|
|
OpBranchConditional %cond2 %80 %80; dup continue edge
|
|
|
|
%79 = OpLabel ; switch merge
|
|
OpStore %var %uint_5
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_6
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 40u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
Continue{}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{6u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_IfBreak_OnTrue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond2 %40 %50
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_3
|
|
; true to if's merge; false to continue
|
|
OpBranchConditional %cond3 %50 %80
|
|
|
|
%50 = OpLabel ; merge for selection
|
|
OpStore %var %uint_4
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_5
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_6
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{6u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_IfBreak_OnFalse) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpSelectionMerge %50 None
|
|
OpBranchConditional %cond2 %40 %50
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_3
|
|
; false to if's merge; true to continue
|
|
OpBranchConditional %cond3 %80 %50
|
|
|
|
%50 = OpLabel ; merge for selection
|
|
OpStore %var %uint_4
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_5
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_6
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{true}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{6u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Fallthrough_OnTrue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %79 40 %40 50 %50
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
OpBranchConditional %cond %50 %80 ; loop continue; fall through on true
|
|
|
|
%50 = OpLabel
|
|
OpStore %var %uint_50
|
|
OpBranch %79
|
|
|
|
%79 = OpLabel ; switch merge
|
|
OpStore %var %uint_3
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 40u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{40u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
Fallthrough{}
|
|
}
|
|
Case 50u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{50u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Fallthrough_OnFalse) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpSelectionMerge %79 None
|
|
OpSwitch %selector %79 40 %40 50 %50
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
OpBranchConditional %cond %80 %50 ; loop continue; fall through on false
|
|
|
|
%50 = OpLabel
|
|
OpStore %var %uint_50
|
|
OpBranch %79
|
|
|
|
%79 = OpLabel ; switch merge
|
|
OpStore %var %uint_3
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 40u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{40u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
Fallthrough{}
|
|
}
|
|
Case 50u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{50u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Forward_OnTrue) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
; continue; forward on true
|
|
OpBranchConditional %cond %40 %80
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Forward_OnFalse) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpLoopMerge %99 %80 None
|
|
OpBranch %30
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
; continue; forward on true
|
|
OpBranchConditional %cond %80 %40
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_3
|
|
OpBranch %80
|
|
|
|
%80 = OpLabel ; continue target
|
|
OpStore %var %uint_4
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{3u}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{4u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_IfBreak_IfBreak_Same) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %99 %99
|
|
|
|
%20 = OpLabel ; dead
|
|
OpStore %var %uint_1
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{0u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{5u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_IfBreak_IfBreak_DifferentIsError) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_0
|
|
OpSelectionMerge %99 None
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %89 None
|
|
OpBranchConditional %cond %30 %89
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpBranchConditional %cond %89 %99 ; invalid divergence
|
|
|
|
%89 = OpLabel ; inner if-merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; outer if-merge
|
|
OpStore %var %uint_5
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(FlowClassifyCFGEdges(&fe));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from block 30 to block 99 is an invalid exit from construct "
|
|
"starting at block 20; branch bypasses merge block 89"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Fallthrough_Fallthrough_Same) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 30 %30
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpBranchConditional %cond %30 %30 ; fallthrough fallthrough
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_30
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_7
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Switch{
|
|
ScalarConstructor[not set]{42u}
|
|
{
|
|
Case 20u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
Fallthrough{}
|
|
}
|
|
Case 30u{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{7u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_Fallthrough_NotLastInCase_IsError) {
|
|
// See also
|
|
// ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Forward_IsError.
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %selector %99 20 %20 40 %40
|
|
|
|
%20 = OpLabel ; case 30
|
|
OpSelectionMerge %39 None
|
|
OpBranchConditional %cond %40 %30 ; fallthrough and forward
|
|
|
|
%30 = OpLabel
|
|
OpBranch %39
|
|
|
|
%39 = OpLabel
|
|
OpBranch %99
|
|
|
|
%40 = OpLabel ; case 40
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(fe.EmitBody());
|
|
// The weird forward branch pulls in 40 as part of the selection rather than
|
|
// as a case.
|
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 30, 39, 99));
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
Eq("Branch from 10 to 40 bypasses header 20 (dominance rule violated)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Forward_Forward_Same) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpStore %var %uint_1
|
|
OpBranchConditional %cond %99 %99; forward
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{1u}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{2u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
EmitBody_BranchConditional_Forward_Forward_Different_IsError) {
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranchConditional %cond %20 %99
|
|
|
|
%20 = OpLabel
|
|
OpReturn
|
|
|
|
%99 = OpLabel
|
|
OpStore %var %uint_2
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(fe.EmitBody());
|
|
EXPECT_THAT(p->error(),
|
|
Eq("Control flow diverges at block 10 (to 20, 99) but it is not "
|
|
"a structured header (it has no merge instruction)"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, Switch_NotAsSelectionHeader_Simple) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSwitch %uint_0 %99
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(fe.EmitBody());
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
HasSubstr("invalid structured control flow: found an OpSwitch that "
|
|
"is not preceded by an OpSelectionMerge:"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest,
|
|
Switch_NotAsSelectionHeader_NonDefaultBranchesAreContinue) {
|
|
// Adapted from SPIRV-Tools test MissingMergeOneUnseenTargetSwitchBad
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
%entry = OpLabel
|
|
OpBranch %loop
|
|
%loop = OpLabel
|
|
OpLoopMerge %merge %cont None
|
|
OpBranchConditional %cond %merge %b1
|
|
|
|
; Here an OpSwitch is used with only one "unseen-so-far" target
|
|
; so it doesn't need an OpSelectionMerge.
|
|
; The %cont target can be implemented via "continue". So we can
|
|
; generate:
|
|
; if ((selector != 1) && (selector != 3)) { continue; }
|
|
%b1 = OpLabel
|
|
OpSwitch %selector %b2 0 %b2 1 %cont 2 %b2 3 %cont
|
|
|
|
%b2 = OpLabel ; the one unseen target
|
|
OpBranch %cont
|
|
%cont = OpLabel
|
|
OpBranchConditional %cond2 %merge %loop
|
|
%merge = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(fe.EmitBody());
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
HasSubstr("invalid structured control flow: found an OpSwitch that "
|
|
"is not preceded by an OpSelectionMerge:"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, Switch_NotAsSelectionHeader_DefaultBranchIsContinue) {
|
|
// Adapted from SPIRV-Tools test MissingMergeOneUnseenTargetSwitchBad
|
|
auto p = parser(test::Assemble(CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
%entry = OpLabel
|
|
OpBranch %loop
|
|
%loop = OpLabel
|
|
OpLoopMerge %merge %cont None
|
|
OpBranchConditional %cond %merge %b1
|
|
|
|
; Here an OpSwitch is used with only one "unseen-so-far" target
|
|
; so it doesn't need an OpSelectionMerge.
|
|
; The %cont target can be implemented via "continue". So we can
|
|
; generate:
|
|
; if (!(selector == 0 || selector == 2)) {continue;}
|
|
%b1 = OpLabel
|
|
OpSwitch %selector %cont 0 %b2 1 %cont 2 %b2
|
|
|
|
%b2 = OpLabel ; the one unseen target
|
|
OpBranch %cont
|
|
%cont = OpLabel
|
|
OpBranchConditional %cond2 %merge %loop
|
|
%merge = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)"));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_FALSE(fe.EmitBody());
|
|
EXPECT_THAT(
|
|
p->error(),
|
|
HasSubstr("invalid structured control flow: found an OpSwitch that "
|
|
"is not preceded by an OpSelectionMerge:"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, SiblingLoopConstruct_Null) {
|
|
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.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_EQ(fe.SiblingLoopConstruct(nullptr), nullptr);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, SiblingLoopConstruct_NotAContinue) {
|
|
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.get(), *spirv_function(p.get(), 100));
|
|
ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
|
|
const Construct* c = fe.GetBlockInfo(10)->construct;
|
|
EXPECT_NE(c, nullptr);
|
|
EXPECT_EQ(fe.SiblingLoopConstruct(c), nullptr);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, SiblingLoopConstruct_SingleBlockLoop) {
|
|
auto assembly = 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
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
|
|
const Construct* c = fe.GetBlockInfo(20)->construct;
|
|
EXPECT_EQ(c->kind, Construct::kContinue);
|
|
EXPECT_EQ(fe.SiblingLoopConstruct(c), nullptr);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, SiblingLoopConstruct_ContinueIsWholeMultiBlockLoop) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %20 None ; continue target is also loop header
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
|
|
<< p->error() << assembly;
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
|
|
const Construct* c = fe.GetBlockInfo(20)->construct;
|
|
EXPECT_EQ(c->kind, Construct::kContinue);
|
|
EXPECT_EQ(fe.SiblingLoopConstruct(c), nullptr);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, SiblingLoopConstruct_HasSiblingLoop) {
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpLoopMerge %99 %30 None
|
|
OpBranchConditional %cond %30 %99
|
|
|
|
%30 = OpLabel ; continue target
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
ASSERT_TRUE(FlowLabelControlFlowConstructs(&fe)) << p->error();
|
|
const Construct* c = fe.GetBlockInfo(30)->construct;
|
|
EXPECT_EQ(c->kind, Construct::kContinue);
|
|
EXPECT_THAT(ToString(fe.SiblingLoopConstruct(c)),
|
|
Eq("Construct{ Loop [1,2) begin_id:20 end_id:30 depth:1 "
|
|
"parent:Function@10 scope:[1,3) in-l:Loop@20 }"));
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_IfSelection_TrueBranch_LoopBreak) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%5 = OpLabel
|
|
OpBranch %10
|
|
|
|
%10 = OpLabel
|
|
OpLoopMerge %99 %90 None
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %99 %30 ; true branch breaking is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; selection merge
|
|
OpBranch %90
|
|
|
|
%90 = OpLabel ; continue target
|
|
OpBranch %10 ; backedge
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_TrueBranch_LoopContinue) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%5 = OpLabel
|
|
OpBranch %10
|
|
|
|
%10 = OpLabel
|
|
OpLoopMerge %99 %90 None
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %90 %30 ; true branch continue is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; selection merge
|
|
OpBranch %90
|
|
|
|
%90 = OpLabel ; continue target
|
|
OpBranch %10 ; backedge
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_TrueBranch_SwitchBreak) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %uint_20 %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %99 %30 ; true branch switch break is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; if-selection merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; switch merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Switch{
|
|
ScalarConstructor[not set]{20u}
|
|
{
|
|
Case 20u{
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_FalseBranch_LoopBreak) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%5 = OpLabel
|
|
OpBranch %10
|
|
|
|
%10 = OpLabel
|
|
OpLoopMerge %99 %90 None
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %30 %99 ; false branch breaking is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; selection merge
|
|
OpBranch %90
|
|
|
|
%90 = OpLabel ; continue target
|
|
OpBranch %10 ; backedge
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_FalseBranch_LoopContinue) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%5 = OpLabel
|
|
OpBranch %10
|
|
|
|
%10 = OpLabel
|
|
OpLoopMerge %99 %90 None
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %30 %90 ; false branch continue is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; selection merge
|
|
OpBranch %90
|
|
|
|
%90 = OpLabel ; continue target
|
|
OpBranch %10 ; backedge
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Loop{
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Continue{}
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got) << p->error();
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_FalseBranch_SwitchBreak) {
|
|
// crbug.com/tint/243
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
|
|
%10 = OpLabel
|
|
OpSelectionMerge %99 None
|
|
OpSwitch %uint_20 %99 20 %20
|
|
|
|
%20 = OpLabel
|
|
OpSelectionMerge %40 None
|
|
OpBranchConditional %cond %30 %99 ; false branch switch break is ok
|
|
|
|
%30 = OpLabel
|
|
OpBranch %40
|
|
|
|
%40 = OpLabel ; if-selection merge
|
|
OpBranch %99
|
|
|
|
%99 = OpLabel ; switch merge
|
|
OpReturn
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Switch{
|
|
ScalarConstructor[not set]{20u}
|
|
{
|
|
Case 20u{
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Break{}
|
|
}
|
|
}
|
|
}
|
|
Default{
|
|
}
|
|
}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got);
|
|
}
|
|
|
|
TEST_F(SpvParserTest, EmitBody_LoopInternallyDiverge_Simple) {
|
|
// crbug.com/tint/524
|
|
auto assembly = CommonTypes() + R"(
|
|
%100 = OpFunction %void None %voidfn
|
|
%10 = OpLabel
|
|
OpStore %var %uint_10
|
|
OpBranch %20
|
|
|
|
%20 = OpLabel
|
|
OpStore %var %uint_20
|
|
OpLoopMerge %99 %90 None
|
|
OpBranchConditional %cond %30 %40 ; divergence
|
|
|
|
%30 = OpLabel
|
|
OpStore %var %uint_30
|
|
OpBranch %90
|
|
|
|
%40 = OpLabel
|
|
OpStore %var %uint_40
|
|
OpBranch %90
|
|
|
|
%90 = OpLabel ; continue target
|
|
OpStore %var %uint_90
|
|
OpBranch %20
|
|
|
|
%99 = OpLabel ; loop merge
|
|
OpStore %var %uint_99
|
|
OpReturn
|
|
|
|
OpFunctionEnd
|
|
)";
|
|
auto p = parser(test::Assemble(assembly));
|
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
|
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
|
auto got = ToString(p->builder(), fe.ast_body());
|
|
auto* expect = R"(Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{10u}
|
|
}
|
|
Loop{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{20u}
|
|
}
|
|
If{
|
|
(
|
|
ScalarConstructor[not set]{false}
|
|
)
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{30u}
|
|
}
|
|
Continue{}
|
|
}
|
|
}
|
|
Else{
|
|
{
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{40u}
|
|
}
|
|
}
|
|
}
|
|
continuing {
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{90u}
|
|
}
|
|
}
|
|
}
|
|
Assignment{
|
|
Identifier[not set]{var_1}
|
|
ScalarConstructor[not set]{99u}
|
|
}
|
|
Return{}
|
|
)";
|
|
ASSERT_EQ(expect, got) << got;
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace spirv
|
|
} // namespace reader
|
|
} // namespace tint
|