| // 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; |
| |
| // Make a local name so it's easier to triage errors. |
| using SpvParserCFGTest = SpvParserTest; |
| |
| 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 |
| OpEntryPoint Fragment %100 "main" |
| OpExecutionMode %100 OriginUpperLeft |
| |
| 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(SpvParserCFGTest, TerminatorsAreValid_SingleBlock) { |
| auto p = parser(test::Assemble(CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| |
| %42 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )")); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_TRUE(fe.TerminatorsAreValid()); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_TRUE(fe.TerminatorsAreValid()) << p->error(); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_TRUE(fe.TerminatorsAreValid()) << p->error(); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_TRUE(fe.TerminatorsAreValid()); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_TRUE(fe.TerminatorsAreValid()); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_TRUE(fe.TerminatorsAreValid()); |
| } |
| |
| TEST_F(SpvParserCFGTest, TerminatorsAreValid_Kill) { |
| auto p = parser(test::Assemble(CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| |
| %10 = OpLabel |
| OpKill |
| |
| OpFunctionEnd |
| )")); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_TRUE(fe.TerminatorsAreValid()); |
| } |
| |
| TEST_F(SpvParserCFGTest, TerminatorsAreValid_Unreachable) { |
| auto p = parser(test::Assemble(CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| |
| %10 = OpLabel |
| OpUnreachable |
| |
| OpFunctionEnd |
| )")); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_TRUE(fe.TerminatorsAreValid()); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_FALSE(fe.TerminatorsAreValid()); |
| EXPECT_THAT(p->error(), Eq("Block 20 branches to function entry block 10")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, RegisterMerges_NoMerges) { |
| auto p = parser(test::Assemble(CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| |
| %10 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )")); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_FALSE(fe.RegisterMerges()); |
| EXPECT_THAT(p->error(), |
| Eq("Structured header block 10 declares invalid merge block 2")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_FALSE(fe.RegisterMerges()); |
| EXPECT_THAT(p->error(), |
| Eq("Structured header block 10 cannot be its own merge block")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_FALSE(fe.RegisterMerges()); |
| EXPECT_THAT(p->error(), |
| Eq("Function entry block 10 cannot be a loop header")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| EXPECT_FALSE(fe.RegisterMerges()); |
| EXPECT_THAT(p->error(), |
| Eq("Structured header 20 declares invalid continue target 999")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, ComputeBlockOrder_OneBlock) { |
| auto p = parser(test::Assemble(CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| |
| %42 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )")); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20)); |
| } |
| |
| TEST_F(SpvParserCFGTest, ComputeBlockOrder_ReorderSequence) { |
| auto p = parser(test::Assemble(CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| |
| %10 = OpLabel |
| OpSelectionMerge %99 None |
| OpBranchConditional %cond %20 %30 |
| |
| %30 = OpLabel |
| OpReturn |
| |
| %20 = OpLabel |
| OpBranch %30 ; backtrack, but does dominate %30 |
| |
| %99 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )")); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 99)); |
| |
| 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); |
| const auto* bi99 = fe.GetBlockInfo(99); |
| ASSERT_NE(bi99, nullptr); |
| EXPECT_EQ(bi99->pos, 3u); |
| } |
| |
| TEST_F(SpvParserCFGTest, ComputeBlockOrder_DupConditionalBranch) { |
| auto p = parser(test::Assemble(CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| |
| %10 = OpLabel |
| OpSelectionMerge %99 None |
| OpBranchConditional %cond %20 %20 |
| |
| %20 = OpLabel |
| OpBranch %99 |
| |
| %99 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )")); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, ComputeBlockOrder_RespectConditionalBranchOrder) { |
| auto p = parser(test::Assemble(CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| |
| %10 = OpLabel |
| OpSelectionMerge %99 None |
| OpBranchConditional %cond %20 %30 |
| |
| %30 = OpLabel |
| OpReturn |
| |
| %20 = OpLabel |
| OpBranch %99 |
| |
| %99 = OpLabel ; dominated by %20, so follow %20 |
| OpReturn |
| |
| OpFunctionEnd |
| )")); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 80, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 40, 20, 30, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, ComputeBlockOrder_RespectSwitchCaseFallthrough) { |
| auto assembly = CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| |
| %10 = OpLabel |
| OpSelectionMerge %99 None |
| ; SPIR-V validation requires a fallthrough destination to immediately |
| ; follow the source. So %20 -> %40, %30 -> %50 |
| OpSwitch %selector %99 20 %20 40 %40 30 %30 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 50, 20, 40, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 30, 40, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| ComputeBlockOrder_RespectSwitchCaseFallthrough_FromCaseToDefaultToCase) { |
| auto assembly = CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| |
| %10 = OpLabel |
| OpSelectionMerge %99 None |
| OpSwitch %selector %80 20 %20 30 %30 |
| |
| %20 = OpLabel |
| OpBranch %80 ; fallthrough to default |
| |
| %80 = OpLabel ; the default case |
| OpBranch %30 ; fallthrough to 30 |
| |
| %30 = OpLabel |
| OpBranch %99 |
| |
| %99 = OpLabel ; dominated by %30, so follow %30 |
| OpReturn |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 30, 99)) << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 50, 40, 20, 30, 99)) |
| << assembly; |
| |
| // We're deliberately testing a case that SPIR-V doesn't allow. |
| p->DeliberatelyInvalidSpirv(); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| ComputeBlockOrder_RespectSwitchCaseFallthrough_Interleaved) { |
| auto assembly = CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| |
| %10 = OpLabel |
| OpSelectionMerge %99 None |
| ; SPIR-V validation requires a fallthrough destination to immediately |
| ; follow the source. So %20 -> %40 |
| OpSwitch %selector %99 20 %20 40 %40 30 %30 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 50, 70, 20, 40, 60, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), |
| ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), |
| ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), |
| ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| %40 = OpLabel |
| OpBranch %49 |
| |
| %49 = OpLabel |
| OpBranch %99 |
| |
| %50 = OpLabel |
| OpSelectionMerge %79 None |
| OpBranchConditional %cond %60 %99 ; break-unless |
| |
| %60 = OpLabel |
| OpBranch %79 |
| |
| %79 = OpLabel ; dominated by 60, so must follow 60 |
| OpBranch %99 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 49, 50, 60, 79, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 45, 49, 50, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99)) |
| << assembly; |
| |
| // Fails SPIR-V validation: |
| // Branch from block 40 to block 99 is an invalid exit from construct starting |
| // at block 30; branch bypasses merge block 49 |
| p->DeliberatelyInvalidSpirv(); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99)) |
| << assembly; |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 must be preceded by a selection merge |
| OpSwitch %selector %99 50 %50 ; default is break, 50 is continue |
| |
| %40 = OpLabel |
| OpBranch %50 |
| |
| %50 = OpLabel |
| OpBranch %20 |
| |
| %99 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| EXPECT_FALSE(p->Parse()); |
| EXPECT_FALSE(p->success()); |
| EXPECT_THAT(p->error(), |
| HasSubstr("OpSwitch must be preceeded by an OpSelectionMerge")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 60, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 60, 70, 89, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| ; Updated SPIR-V rule: |
| ; OpSwitch must be preceded by a selection. |
| OpSwitch %selector %20 99 %99 |
| |
| %99 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| EXPECT_FALSE(p->Parse()); |
| EXPECT_FALSE(p->success()); |
| EXPECT_THAT(p->error(), |
| HasSubstr("OpSwitch must be preceeded by an OpSelectionMerge")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), |
| ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), |
| ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), |
| ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), |
| ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), |
| ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); |
| |
| p->DeliberatelyInvalidSpirv(); |
| // SPIR-V validation fails: |
| // block <ID> 40[%40] exits the continue headed by <ID> 40[%40], but not |
| // via a structured exit" |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| |
| EXPECT_THAT(fe.block_order(), |
| ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); |
| |
| p->DeliberatelyInvalidSpirv(); |
| // SPIR-V validation fails: |
| // block <ID> 40[%40] exits the continue headed by <ID> 40[%40], but not |
| // via a structured exit" |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| fe.RegisterMerges(); |
| EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder()); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| fe.RegisterMerges(); |
| EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder()) << p->error(); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| fe.RegisterMerges(); |
| EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder()); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| OpBranch %30 |
| |
| %30 = OpLabel |
| OpBranch %40 |
| |
| %40 = OpLabel |
| OpBranch %50 |
| |
| %50 = OpLabel |
| OpBranchConditional %cond %20 %99 |
| |
| %99 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 %89 |
| |
| %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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 %89 |
| |
| %89 = OpLabel |
| OpBranch %99 |
| |
| %99 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 %99 |
| |
| %99 = OpLabel ; merge for the if |
| OpReturn |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| fe.RegisterBasicBlocks(); |
| fe.ComputeBlockOrderAndPositions(); |
| EXPECT_TRUE(fe.VerifyHeaderContinueMergeOrder()); |
| fe.RegisterMerges(); |
| fe.LabelControlFlowConstructs(); |
| EXPECT_TRUE(fe.FindSwitchCaseHeaders()); |
| |
| // TODO(crbug.com/tint/774) Re-enable after codegen bug fixed. |
| p->DeliberatelyInvalidSpirv(); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_FALSE(FlowClassifyCFGEdges(&fe)); |
| EXPECT_THAT(p->error(), Eq("Invalid function exit at block 50 from continue " |
| "construct starting at 50")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_FALSE(FlowClassifyCFGEdges(&fe)); |
| EXPECT_THAT(p->error(), Eq("Invalid function exit at block 50 from continue " |
| "construct starting at 50")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_FALSE(FlowClassifyCFGEdges(&fe)); |
| EXPECT_THAT( |
| p->error(), |
| Eq("Invalid backedge (30->20): 30 is not in a continue construct")); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_FALSE(FlowClassifyCFGEdges(&fe)); |
| EXPECT_THAT(p->error(), Eq("Branch from 10 to 50 bypasses continue target 40 " |
| "(dominance rule violated)")); |
| } |
| |
| TEST_F( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_FALSE(FlowClassifyCFGEdges(&fe)); |
| EXPECT_THAT( |
| p->error(), |
| Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)")); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_FALSE(FlowClassifyCFGEdges(&fe)); |
| EXPECT_THAT( |
| p->error(), |
| Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_FALSE(FlowClassifyCFGEdges(&fe)); |
| EXPECT_THAT( |
| p->error(), |
| Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)")); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_FALSE(FlowClassifyCFGEdges(&fe)); |
| EXPECT_THAT( |
| p->error(), |
| Eq("Branch from 10 to 50 bypasses header 20 (dominance rule violated)")); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_FALSE(FlowClassifyCFGEdges(&fe)); |
| EXPECT_THAT( |
| p->error(), |
| Eq("Invalid backedge (30->20): 30 is not in a continue construct")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, FindIfSelectionInternalHeaders_NoIf) { |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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("")); |
| |
| // TODO(crbug.com/tint/775): The SPIR-V reader errors out on this case. |
| // Remove this when it's fixed. |
| p->DeliberatelyInvalidSpirv(); |
| } |
| |
| TEST_F( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe)); |
| EXPECT_THAT(p->error(), Eq("")); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe)); |
| EXPECT_THAT(p->error(), Eq("")); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe)); |
| EXPECT_THAT(p->error(), Eq("")); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe)); |
| EXPECT_THAT(p->error(), Eq("")); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe)); |
| EXPECT_THAT(p->error(), Eq("")); |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(100); |
| EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe)); |
| EXPECT_THAT(p->error(), Eq("")); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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 |
| none |
| undefined |
| __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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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 |
| none |
| undefined |
| __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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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 |
| none |
| undefined |
| __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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, EmitBody_ReturnValue_Loop) { |
| auto p = parser(test::Assemble(CommonTypes() + R"( |
| %200 = OpFunction %uint None %uintfn |
| |
| %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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Unconditional) { // NOLINT - line length |
| 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 |
| ; This is invalid as there must be a backedge to the loop header. |
| ; The SPIR-V allows this and understands how to emit it, even if it's not |
| ; permitted by the SPIR-V validator. |
| |
| %99 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )")); |
| |
| p->DeliberatelyInvalidSpirv(); |
| |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional) { // NOLINT - line length |
| 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 |
| OpBranchConditional %cond %20 %99 ; backedge, and exit |
| |
| %99 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )")); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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} |
| } |
| If{ |
| ( |
| ScalarConstructor[not set]{false} |
| ) |
| { |
| } |
| } |
| Else{ |
| { |
| Break{} |
| } |
| } |
| } |
| } |
| Return{} |
| )"; |
| ASSERT_EQ(expect, got); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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( |
| SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, |
| 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(100); |
| EXPECT_EQ(fe.SiblingLoopConstruct(nullptr), nullptr); |
| } |
| |
| TEST_F(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| OpBranch %30 |
| |
| %30 = OpLabel |
| OpBranchConditional %cond %20 %99 |
| |
| %99 = OpLabel |
| OpReturn |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) |
| << p->error() << assembly; |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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 |
| |
| OpFunctionEnd |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); |
| auto fe = p->function_emitter(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(SpvParserCFGTest, 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(); |
| auto fe = p->function_emitter(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; |
| } |
| |
| TEST_F(SpvParserCFGTest, |
| EmitBody_ContinueFromSingleBlockLoopToOuterLoop_IsError) { |
| // crbug.com/tint/793 |
| // This is invalid SPIR-V but the validator was only recently upgraded |
| // to catch it. |
| auto assembly = CommonTypes() + R"( |
| %100 = OpFunction %void None %voidfn |
| %5 = OpLabel |
| OpBranch %10 |
| |
| %10 = OpLabel ; outer loop header |
| OpLoopMerge %99 %89 None |
| OpBranchConditional %cond %99 %20 |
| |
| %20 = OpLabel ; inner loop single block loop |
| OpLoopMerge %79 %20 None |
| |
| ; true -> continue to outer loop |
| ; false -> self-loop |
| ; The true branch is invalid because a "continue block", i.e. the block |
| ; containing the branch to the continue target, "is valid only for the |
| ; innermost loop it is nested inside of". |
| ; So it can't branch to the continue target of an outer loop. |
| OpBranchConditional %cond %89 %20 |
| |
| %79 = OpLabel ; merge for outer loop |
| OpUnreachable |
| |
| %89 = OpLabel |
| OpBranch %10 ; backedge for outer loop |
| |
| %99 = OpLabel ; merge for outer |
| OpReturn |
| |
| OpFunctionEnd |
| |
| )"; |
| auto p = parser(test::Assemble(assembly)); |
| EXPECT_FALSE(p->Parse()); |
| EXPECT_FALSE(p->success()); |
| EXPECT_THAT(p->error(), |
| HasSubstr("block <ID> 20[%20] exits the continue headed by <ID> " |
| "20[%20], but not via a structured exit")) |
| << p->error(); |
| } |
| |
| } // namespace |
| } // namespace spirv |
| } // namespace reader |
| } // namespace tint |