// 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 #include #include "gmock/gmock.h" #include "src/reader/spirv/function.h" #include "src/reader/spirv/parser_impl.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::ElementsAre; using ::testing::Eq; std::string CommonTypes() { return R"( OpCapability Shader OpMemoryModel Logical Simple %void = OpTypeVoid %voidfn = OpTypeFunction %void %bool = OpTypeBool %cond = OpUndef %bool %cond2 = OpUndef %bool %cond3 = OpUndef %bool %uint = OpTypeInt 32 0 %selector = OpUndef %uint %999 = OpConstant %uint 999 )"; } TEST_F(SpvParserTest, TerminatorsAreSane_SingleBlock) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %42 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.TerminatorsAreSane()); } TEST_F(SpvParserTest, TerminatorsAreSane_Sequence) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %20 = OpLabel OpBranch %30 %30 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.TerminatorsAreSane()) << p->error(); } TEST_F(SpvParserTest, TerminatorsAreSane_If) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %20 = OpLabel OpSelectionMerge %99 None OpBranchConditional %cond %30 %40 %30 = OpLabel OpBranch %99 %40 = OpLabel OpBranch %99 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.TerminatorsAreSane()) << p->error(); } TEST_F(SpvParserTest, TerminatorsAreSane_Switch) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %80 20 %20 30 %30 %20 = OpLabel OpBranch %30 ; fall through %30 = OpLabel OpBranch %99 %80 = OpLabel OpBranch %99 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.TerminatorsAreSane()); } TEST_F(SpvParserTest, TerminatorsAreSane_Loop_SingleBlock) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %20 None OpBranchConditional %cond %20 %99 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.TerminatorsAreSane()); } TEST_F(SpvParserTest, TerminatorsAreSane_Loop_Simple) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %40 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranch %40 %40 = OpLabel OpBranch %20 ; back edge %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.TerminatorsAreSane()); } TEST_F(SpvParserTest, TerminatorsAreSane_Kill) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpKill OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.TerminatorsAreSane()); } TEST_F(SpvParserTest, TerminatorsAreSane_Unreachable) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpUnreachable OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.TerminatorsAreSane()); } TEST_F(SpvParserTest, TerminatorsAreSane_MissingTerminator) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpFunctionEnd )")); // The SPIRV-Tools internal representation rejects this case earlier. EXPECT_FALSE(p->BuildAndParseInternalModuleExceptFunctions()); } TEST_F(SpvParserTest, TerminatorsAreSane_DisallowLoopToEntryBlock) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpBranch %10 ; not allowed OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.TerminatorsAreSane()); EXPECT_THAT(p->error(), Eq("Block 20 branches to function entry block 10")); } TEST_F(SpvParserTest, TerminatorsAreSane_DisallowNonBlock) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %999 ; definitely wrong OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.TerminatorsAreSane()); EXPECT_THAT(p->error(), Eq("Block 10 in function 100 branches to 999 which is " "not a block in the function")); } TEST_F(SpvParserTest, TerminatorsAreSane_DisallowBlockInDifferentFunction) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %210 OpFunctionEnd %200 = OpFunction %void None %voidfn %210 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.TerminatorsAreSane()); EXPECT_THAT(p->error(), Eq("Block 10 in function 100 branches to 210 which " "is not a block in the function")); } TEST_F(SpvParserTest, RegisterMerges_NoMerges) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.RegisterMerges()); const auto* bi = fe.GetBlockInfo(10); ASSERT_NE(bi, nullptr); EXPECT_EQ(bi->merge_for_header, 0u); EXPECT_EQ(bi->continue_for_header, 0u); EXPECT_EQ(bi->header_for_merge, 0u); EXPECT_EQ(bi->header_for_continue, 0u); EXPECT_FALSE(bi->single_block_loop); } TEST_F(SpvParserTest, RegisterMerges_GoodSelectionMerge_BranchConditional) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpBranchConditional %cond %20 %99 %20 = OpLabel OpBranch %99 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.RegisterMerges()); // Header points to the merge const auto* bi10 = fe.GetBlockInfo(10); ASSERT_NE(bi10, nullptr); EXPECT_EQ(bi10->merge_for_header, 99u); EXPECT_EQ(bi10->continue_for_header, 0u); EXPECT_EQ(bi10->header_for_merge, 0u); EXPECT_EQ(bi10->header_for_continue, 0u); EXPECT_FALSE(bi10->single_block_loop); // Middle block is neither header nor merge const auto* bi20 = fe.GetBlockInfo(20); ASSERT_NE(bi20, nullptr); EXPECT_EQ(bi20->merge_for_header, 0u); EXPECT_EQ(bi20->continue_for_header, 0u); EXPECT_EQ(bi20->header_for_merge, 0u); EXPECT_EQ(bi20->header_for_continue, 0u); EXPECT_FALSE(bi20->single_block_loop); // Merge block points to the header const auto* bi99 = fe.GetBlockInfo(99); ASSERT_NE(bi99, nullptr); EXPECT_EQ(bi99->merge_for_header, 0u); EXPECT_EQ(bi99->continue_for_header, 0u); EXPECT_EQ(bi99->header_for_merge, 10u); EXPECT_EQ(bi99->header_for_continue, 0u); EXPECT_FALSE(bi99->single_block_loop); } TEST_F(SpvParserTest, RegisterMerges_GoodSelectionMerge_Switch) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %99 20 %20 %20 = OpLabel OpBranch %99 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.RegisterMerges()); // Header points to the merge const auto* bi10 = fe.GetBlockInfo(10); ASSERT_NE(bi10, nullptr); EXPECT_EQ(bi10->merge_for_header, 99u); EXPECT_EQ(bi10->continue_for_header, 0u); EXPECT_EQ(bi10->header_for_merge, 0u); EXPECT_EQ(bi10->header_for_continue, 0u); EXPECT_FALSE(bi10->single_block_loop); // Middle block is neither header nor merge const auto* bi20 = fe.GetBlockInfo(20); ASSERT_NE(bi20, nullptr); EXPECT_EQ(bi20->merge_for_header, 0u); EXPECT_EQ(bi20->continue_for_header, 0u); EXPECT_EQ(bi20->header_for_merge, 0u); EXPECT_EQ(bi20->header_for_continue, 0u); EXPECT_FALSE(bi20->single_block_loop); // Merge block points to the header const auto* bi99 = fe.GetBlockInfo(99); ASSERT_NE(bi99, nullptr); EXPECT_EQ(bi99->merge_for_header, 0u); EXPECT_EQ(bi99->continue_for_header, 0u); EXPECT_EQ(bi99->header_for_merge, 10u); EXPECT_EQ(bi99->header_for_continue, 0u); EXPECT_FALSE(bi99->single_block_loop); } TEST_F(SpvParserTest, RegisterMerges_GoodLoopMerge_SingleBlockLoop) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %20 None OpBranchConditional %cond %20 %99 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.RegisterMerges()); // Entry block is not special const auto* bi10 = fe.GetBlockInfo(10); ASSERT_NE(bi10, nullptr); EXPECT_EQ(bi10->merge_for_header, 0u); EXPECT_EQ(bi10->continue_for_header, 0u); EXPECT_EQ(bi10->header_for_merge, 0u); EXPECT_EQ(bi10->header_for_continue, 0u); EXPECT_FALSE(bi10->single_block_loop); // Single block loop is its own continue, and marked as single block loop. const auto* bi20 = fe.GetBlockInfo(20); ASSERT_NE(bi20, nullptr); EXPECT_EQ(bi20->merge_for_header, 99u); EXPECT_EQ(bi20->continue_for_header, 20u); EXPECT_EQ(bi20->header_for_merge, 0u); EXPECT_EQ(bi20->header_for_continue, 20u); EXPECT_TRUE(bi20->single_block_loop); // Merge block points to the header const auto* bi99 = fe.GetBlockInfo(99); ASSERT_NE(bi99, nullptr); EXPECT_EQ(bi99->merge_for_header, 0u); EXPECT_EQ(bi99->continue_for_header, 0u); EXPECT_EQ(bi99->header_for_merge, 20u); EXPECT_EQ(bi99->header_for_continue, 0u); EXPECT_FALSE(bi99->single_block_loop); } TEST_F(SpvParserTest, RegisterMerges_GoodLoopMerge_MultiBlockLoop_Branch) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %40 None OpBranch %30 %30 = OpLabel OpBranchConditional %cond %40 %99 %40 = OpLabel OpBranch %20 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.RegisterMerges()); // Loop header points to continue and merge const auto* bi20 = fe.GetBlockInfo(20); ASSERT_NE(bi20, nullptr); EXPECT_EQ(bi20->merge_for_header, 99u); EXPECT_EQ(bi20->continue_for_header, 40u); EXPECT_EQ(bi20->header_for_merge, 0u); EXPECT_EQ(bi20->header_for_continue, 0u); EXPECT_FALSE(bi20->single_block_loop); // Continue block points to header const auto* bi40 = fe.GetBlockInfo(40); ASSERT_NE(bi40, nullptr); EXPECT_EQ(bi40->merge_for_header, 0u); EXPECT_EQ(bi40->continue_for_header, 0u); EXPECT_EQ(bi40->header_for_merge, 0u); EXPECT_EQ(bi40->header_for_continue, 20u); EXPECT_FALSE(bi40->single_block_loop); // Merge block points to the header const auto* bi99 = fe.GetBlockInfo(99); ASSERT_NE(bi99, nullptr); EXPECT_EQ(bi99->merge_for_header, 0u); EXPECT_EQ(bi99->continue_for_header, 0u); EXPECT_EQ(bi99->header_for_merge, 20u); EXPECT_EQ(bi99->header_for_continue, 0u); EXPECT_FALSE(bi99->single_block_loop); } TEST_F(SpvParserTest, RegisterMerges_GoodLoopMerge_MultiBlockLoop_BranchConditional) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %40 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranch %40 %40 = OpLabel OpBranch %20 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_TRUE(fe.RegisterMerges()); // Loop header points to continue and merge const auto* bi20 = fe.GetBlockInfo(20); ASSERT_NE(bi20, nullptr); EXPECT_EQ(bi20->merge_for_header, 99u); EXPECT_EQ(bi20->continue_for_header, 40u); EXPECT_EQ(bi20->header_for_merge, 0u); EXPECT_EQ(bi20->header_for_continue, 0u); EXPECT_FALSE(bi20->single_block_loop); // Continue block points to header const auto* bi40 = fe.GetBlockInfo(40); ASSERT_NE(bi40, nullptr); EXPECT_EQ(bi40->merge_for_header, 0u); EXPECT_EQ(bi40->continue_for_header, 0u); EXPECT_EQ(bi40->header_for_merge, 0u); EXPECT_EQ(bi40->header_for_continue, 20u); EXPECT_FALSE(bi40->single_block_loop); // Merge block points to the header const auto* bi99 = fe.GetBlockInfo(99); ASSERT_NE(bi99, nullptr); EXPECT_EQ(bi99->merge_for_header, 0u); EXPECT_EQ(bi99->continue_for_header, 0u); EXPECT_EQ(bi99->header_for_merge, 20u); EXPECT_EQ(bi99->header_for_continue, 0u); EXPECT_FALSE(bi99->single_block_loop); } TEST_F(SpvParserTest, RegisterMerges_SelectionMerge_BadTerminator) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpBranch %30 %20 = OpLabel OpBranch %99 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.RegisterMerges()); EXPECT_THAT(p->error(), Eq("Selection header 10 does not end in an " "OpBranchConditional or OpSwitch instruction")); } TEST_F(SpvParserTest, RegisterMerges_LoopMerge_BadTerminator) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %40 None OpSwitch %selector %99 30 %30 %30 = OpLabel OpBranch %20 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.RegisterMerges()); EXPECT_THAT(p->error(), Eq("Loop header 20 does not end in an OpBranch or " "OpBranchConditional instruction")); } TEST_F(SpvParserTest, RegisterMerges_BadMergeBlock) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %void None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranch %99 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.RegisterMerges()); EXPECT_THAT(p->error(), Eq("Structured header block 10 declares invalid merge block 1")); } TEST_F(SpvParserTest, RegisterMerges_HeaderIsItsOwnMerge) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %10 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranch %99 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.RegisterMerges()); EXPECT_THAT(p->error(), Eq("Structured header block 10 cannot be its own merge block")); } TEST_F(SpvParserTest, RegisterMerges_MergeReused) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %49 None OpBranchConditional %cond %20 %49 %20 = OpLabel OpBranch %49 %49 = OpLabel OpBranch %50 %50 = OpLabel OpSelectionMerge %49 None ; can't reuse merge block OpBranchConditional %cond %60 %99 %60 = OpLabel OpBranch %99 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.RegisterMerges()); EXPECT_THAT( p->error(), Eq("Block 49 declared as merge block for more than one header: 10, 50")); } TEST_F(SpvParserTest, RegisterMerges_EntryBlockIsLoopHeader) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpLoopMerge %99 %30 None OpBranchConditional %cond %10 %99 %30 = OpLabel OpBranch %10 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.RegisterMerges()); EXPECT_THAT(p->error(), Eq("Function entry block 10 cannot be a loop header")); } TEST_F(SpvParserTest, RegisterMerges_BadContinueTarget) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %999 None OpBranchConditional %cond %20 %99 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.RegisterMerges()); EXPECT_THAT(p->error(), Eq("Structured header 20 declares invalid continue target 999")); } TEST_F(SpvParserTest, RegisterMerges_MergeSameAsContinue) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %50 %50 None OpBranchConditional %cond %20 %99 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.RegisterMerges()); EXPECT_THAT(p->error(), Eq("Invalid structured header block 20: declares block 50 as " "both its merge block and continue target")); } TEST_F(SpvParserTest, RegisterMerges_ContinueReused) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %49 %40 None OpBranchConditional %cond %30 %49 %30 = OpLabel OpBranch %40 %40 = OpLabel OpBranch %20 %49 = OpLabel OpBranch %50 %50 = OpLabel OpLoopMerge %99 %40 None OpBranchConditional %cond %60 %99 %60 = OpLabel OpBranch %70 %70 = OpLabel OpBranch %50 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.RegisterMerges()); EXPECT_THAT(p->error(), Eq("Block 40 declared as continue target for more " "than one header: 20, 50")); } TEST_F(SpvParserTest, RegisterMerges_SingleBlockLoop_NotItsOwnContinue) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %30 None OpBranchConditional %cond %20 %99 %30 = OpLabel OpBranch %20 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.RegisterMerges()); EXPECT_THAT( p->error(), Eq("Block 20 branches to itself but is not its own continue target")); } TEST_F(SpvParserTest, RegisterMerges_NotSingleBlockLoop_IsItsOwnContinue) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %20 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranch %20 %99 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); EXPECT_FALSE(fe.RegisterMerges()); EXPECT_THAT(p->error(), Eq("Loop header block 20 declares itself as its own " "continue target, but does not branch to itself")); } TEST_F(SpvParserTest, ComputeBlockOrder_OneBlock) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %42 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(42)); const auto* bi = fe.GetBlockInfo(42); ASSERT_NE(bi, nullptr); EXPECT_EQ(bi->pos, 0u); } TEST_F(SpvParserTest, ComputeBlockOrder_IgnoreStaticalyUnreachable) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %15 = OpLabel ; statically dead OpReturn %20 = OpLabel OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20)); } TEST_F(SpvParserTest, ComputeBlockOrder_KillIsDeadEnd) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %15 = OpLabel ; statically dead OpReturn %20 = OpLabel OpKill ; Kill doesn't lead anywhere OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20)); } TEST_F(SpvParserTest, ComputeBlockOrder_UnreachableIsDeadEnd) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %15 = OpLabel ; statically dead OpReturn %20 = OpLabel OpUnreachable ; Unreachable doesn't lead anywhere OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20)); } TEST_F(SpvParserTest, ComputeBlockOrder_ReorderSequence) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %30 = OpLabel OpReturn %20 = OpLabel OpBranch %30 ; backtrack OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30)); const auto* bi10 = fe.GetBlockInfo(10); ASSERT_NE(bi10, nullptr); EXPECT_EQ(bi10->pos, 0u); const auto* bi20 = fe.GetBlockInfo(20); ASSERT_NE(bi20, nullptr); EXPECT_EQ(bi20->pos, 1u); const auto* bi30 = fe.GetBlockInfo(30); ASSERT_NE(bi30, nullptr); EXPECT_EQ(bi30->pos, 2u); } TEST_F(SpvParserTest, ComputeBlockOrder_DupConditionalBranch) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpBranchConditional %cond %20 %20 %99 = OpLabel OpReturn %20 = OpLabel OpBranch %99 OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_RespectConditionalBranchOrder) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpBranchConditional %cond %20 %30 %99 = OpLabel OpReturn %30 = OpLabel OpReturn %20 = OpLabel OpBranch %99 OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_TrueOnlyBranch) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpBranchConditional %cond %20 %99 %99 = OpLabel OpReturn %20 = OpLabel OpBranch %99 OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_FalseOnlyBranch) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpBranchConditional %cond %99 %20 %99 = OpLabel OpReturn %20 = OpLabel OpBranch %99 OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_SwitchOrderNaturallyReversed) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %99 20 %20 30 %30 %99 = OpLabel OpReturn %30 = OpLabel OpReturn %20 = OpLabel OpBranch %99 OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_SwitchWithDefaultOrderNaturallyReversed) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %80 20 %20 30 %30 %80 = OpLabel ; the default case OpBranch %99 %99 = OpLabel OpReturn %30 = OpLabel OpReturn %20 = OpLabel OpBranch %99 OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 20, 80, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Switch_DefaultSameAsACase) { auto* p = parser(test::Assemble(CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %30 20 %20 30 %30 40 %40 %99 = OpLabel OpReturn %30 = OpLabel OpBranch %99 %20 = OpLabel OpBranch %99 %40 = OpLabel OpBranch %99 OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 40, 20, 30, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_RespectSwitchCaseFallthrough) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %99 20 %20 30 %30 40 %40 50 %50 %50 = OpLabel OpBranch %99 %99 = OpLabel OpReturn %40 = OpLabel OpBranch %99 %30 = OpLabel OpBranch %50 ; fallthrough %20 = OpLabel OpBranch %40 ; fallthrough OpFunctionEnd )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 50, 20, 40, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_RespectSwitchCaseFallthrough_FromDefault) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %80 20 %20 30 %30 40 %40 %80 = OpLabel ; the default case OpBranch %30 ; fallthrough to another case %99 = OpLabel OpReturn %40 = OpLabel OpBranch %99 %30 = OpLabel OpBranch %40 %20 = OpLabel OpBranch %99 OpFunctionEnd )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 30, 40, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_RespectSwitchCaseFallthrough_FromCaseToDefaultToCase) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %80 20 %20 30 %30 %99 = OpLabel OpReturn %20 = OpLabel OpBranch %80 ; fallthrough to default %80 = OpLabel ; the default case OpBranch %30 ; fallthrough to 30 %30 = OpLabel OpBranch %99 OpFunctionEnd )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 80, 30, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_SwitchCasesFallthrough_OppositeDirections) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %99 20 %20 30 %30 40 %40 50 %50 %99 = OpLabel OpReturn %20 = OpLabel OpBranch %30 ; forward %40 = OpLabel OpBranch %99 %30 = OpLabel OpBranch %99 ; SPIR-V doesn't actually allow a fall-through that goes backward in the ; module. But the block ordering algorithm tolerates it. %50 = OpLabel OpBranch %40 ; backward OpFunctionEnd )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 50, 40, 20, 30, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_RespectSwitchCaseFallthrough_Interleaved) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %99 20 %20 30 %30 40 %40 50 %50 %99 = OpLabel OpReturn %20 = OpLabel OpBranch %40 %30 = OpLabel OpBranch %50 %40 = OpLabel OpBranch %60 %50 = OpLabel OpBranch %70 %60 = OpLabel OpBranch %99 %70 = OpLabel OpBranch %99 OpFunctionEnd )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 30, 50, 70, 20, 40, 60, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Nest_If_Contains_If) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpBranchConditional %cond %20 %50 %99 = OpLabel OpReturn %20 = OpLabel OpSelectionMerge %49 None OpBranchConditional %cond %30 %40 %49 = OpLabel OpBranch %99 %30 = OpLabel OpBranch %49 %40 = OpLabel OpBranch %49 %50 = OpLabel OpSelectionMerge %79 None OpBranchConditional %cond %60 %70 %79 = OpLabel OpBranch %99 %60 = OpLabel OpBranch %79 %70 = OpLabel OpBranch %79 OpFunctionEnd )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Nest_If_In_SwitchCase) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %50 20 %20 50 %50 %99 = OpLabel OpReturn %20 = OpLabel OpSelectionMerge %49 None OpBranchConditional %cond %30 %40 %49 = OpLabel OpBranch %99 %30 = OpLabel OpBranch %49 %40 = OpLabel OpBranch %49 %50 = OpLabel OpSelectionMerge %79 None OpBranchConditional %cond %60 %70 %79 = OpLabel OpBranch %99 %60 = OpLabel OpBranch %79 %70 = OpLabel OpBranch %79 OpFunctionEnd )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Nest_IfFallthrough_In_SwitchCase) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %50 20 %20 50 %50 %99 = OpLabel OpReturn %20 = OpLabel OpSelectionMerge %49 None OpBranchConditional %cond %30 %40 %49 = OpLabel OpBranchConditional %cond %99 %50 ; fallthrough %30 = OpLabel OpBranch %49 %40 = OpLabel OpBranch %49 %50 = OpLabel OpSelectionMerge %79 None OpBranchConditional %cond %60 %70 %79 = OpLabel OpBranch %99 %60 = OpLabel OpBranch %79 %70 = OpLabel OpBranch %79 OpFunctionEnd )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 60, 70, 79, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Nest_IfBreak_In_SwitchCase) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpSelectionMerge %99 None OpSwitch %selector %50 20 %20 50 %50 %99 = OpLabel OpReturn %20 = OpLabel OpSelectionMerge %49 None OpBranchConditional %cond %99 %40 ; break-if %49 = OpLabel OpBranch %99 %40 = OpLabel OpBranch %49 %50 = OpLabel OpSelectionMerge %79 None OpBranchConditional %cond %60 %99 ; break-unless %79 = OpLabel OpBranch %99 %60 = OpLabel OpBranch %79 OpFunctionEnd )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 49, 50, 60, 79, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_SingleBlock_Simple) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn ; The entry block can't be the target of a branch %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %20 None OpBranchConditional %cond %20 %99 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_SingleBlock_Infinite) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn ; The entry block can't be the target of a branch %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %20 None OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_SingleBlock_DupInfinite) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn ; The entry block can't be the target of a branch %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %20 None OpBranchConditional %cond %20 %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_HeaderHasBreakIf) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 ; like While %30 = OpLabel ; trivial body OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_HeaderHasBreakUnless) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %99 %30 ; has break-unless %30 = OpLabel ; trivial body OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasBreak) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranch %99 ; break %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasBreakIf) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranchConditional %cond2 %99 %40 ; break-if %40 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasBreakUnless) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranchConditional %cond2 %40 %99 ; break-unless %40 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_If) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpSelectionMerge %49 None OpBranchConditional %cond2 %40 %45 ; nested if %40 = OpLabel OpBranch %49 %45 = OpLabel OpBranch %49 %49 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 45, 49, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_If_Break) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpSelectionMerge %49 None OpBranchConditional %cond2 %40 %49 ; nested if %40 = OpLabel OpBranch %99 ; break from nested if %49 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasContinueIf) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranchConditional %cond2 %50 %40 ; continue-if %40 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasContinueUnless) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranchConditional %cond2 %40 %50 ; continue-unless %40 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_If_Continue) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpSelectionMerge %49 None OpBranchConditional %cond2 %40 %49 ; nested if %40 = OpLabel OpBranch %50 ; continue from nested if %49 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 40, 49, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_Switch) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpSelectionMerge %49 None OpSwitch %selector %49 40 %40 45 %45 ; fully nested switch %40 = OpLabel OpBranch %49 %45 = OpLabel OpBranch %49 %49 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_Switch_CaseBreaks) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpSelectionMerge %49 None OpSwitch %selector %49 40 %40 45 %45 %40 = OpLabel ; This case breaks out of the loop. This is not possible in C ; because "break" will escape the switch only. OpBranch %99 %45 = OpLabel OpBranch %49 %49 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Body_Switch_CaseContinues) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpSelectionMerge %49 None OpSwitch %selector %49 40 %40 45 %45 %40 = OpLabel OpBranch %50 ; continue bypasses switch merge %45 = OpLabel OpBranch %49 %49 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 45, 40, 49, 50, 99)) << assembly; } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_BodyHasSwitchContinueBreak) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpSwitch %selector %99 50 %50 ; default is break, 50 is continue %40 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Continue_Sequence) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranch %50 %50 = OpLabel OpBranch %60 %60 = OpLabel OpBranch %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 60, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Continue_ContainsIf) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranch %50 %50 = OpLabel OpSelectionMerge %89 None OpBranchConditional %cond2 %60 %70 %89 = OpLabel OpBranch %20 ; backedge %60 = OpLabel OpBranch %89 %70 = OpLabel OpBranch %89 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 60, 70, 89, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Continue_HasBreakIf) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranch %50 %50 = OpLabel OpBranchConditional %cond2 %99 %20 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Continue_HasBreakUnless) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranch %50 %50 = OpLabel OpBranchConditional %cond2 %20 %99 %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Continue_SwitchBreak) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpBranch %50 %50 = OpLabel OpSwitch %selector %20 99 %99 ; yes, this is obtuse but valid %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 50, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpLoopMerge %49 %40 None OpBranchConditional %cond2 %35 %49 %35 = OpLabel OpBranch %37 %37 = OpLabel OpBranch %40 %40 = OpLabel ; inner loop's continue OpBranch %30 ; backedge %49 = OpLabel ; inner loop's merge OpBranch %50 %50 = OpLabel ; outer loop's continue OpBranch %20 ; outer loop's backege %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop_InnerBreak) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpLoopMerge %49 %40 None OpBranchConditional %cond2 %35 %49 %35 = OpLabel OpBranchConditional %cond3 %49 %37 ; break to inner merge %37 = OpLabel OpBranch %40 %40 = OpLabel ; inner loop's continue OpBranch %30 ; backedge %49 = OpLabel ; inner loop's merge OpBranch %50 %50 = OpLabel ; outer loop's continue OpBranch %20 ; outer loop's backege %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop_InnerContinue) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpLoopMerge %49 %40 None OpBranchConditional %cond2 %35 %49 %35 = OpLabel OpBranchConditional %cond3 %37 %49 ; continue to inner continue target %37 = OpLabel OpBranch %40 %40 = OpLabel ; inner loop's continue OpBranch %30 ; backedge %49 = OpLabel ; inner loop's merge OpBranch %50 %50 = OpLabel ; outer loop's continue OpBranch %20 ; outer loop's backege %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop_InnerContinueBreaks) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpLoopMerge %49 %40 None OpBranchConditional %cond2 %35 %49 %35 = OpLabel OpBranch %37 %37 = OpLabel OpBranch %40 %40 = OpLabel ; inner loop's continue OpBranchConditional %cond3 %30 %49 ; backedge and inner break %49 = OpLabel ; inner loop's merge OpBranch %50 %50 = OpLabel ; outer loop's continue OpBranch %20 ; outer loop's backege %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop_InnerContinueContinues) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpLoopMerge %49 %40 None OpBranchConditional %cond2 %35 %49 %35 = OpLabel OpBranch %37 %37 = OpLabel OpBranch %40 %40 = OpLabel ; inner loop's continue OpBranchConditional %cond3 %30 %50 ; backedge and continue to outer %49 = OpLabel ; inner loop's merge OpBranch %50 %50 = OpLabel ; outer loop's continue OpBranch %20 ; outer loop's backege %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); } TEST_F(SpvParserTest, ComputeBlockOrder_Loop_Loop_SwitchBackedgeBreakContinue) { auto assembly = CommonTypes() + R"( %100 = OpFunction %void None %voidfn %10 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %99 %50 None OpBranchConditional %cond %30 %99 %30 = OpLabel OpLoopMerge %49 %40 None OpBranchConditional %cond2 %35 %49 %35 = OpLabel OpBranch %37 %37 = OpLabel OpBranch %40 %40 = OpLabel ; inner loop's continue ; This switch does triple duty: ; default -> backedge ; 49 -> loop break ; 49 -> inner loop break ; 50 -> outer loop continue OpSwitch %selector %30 49 %49 50 %50 %49 = OpLabel ; inner loop's merge OpBranch %50 %50 = OpLabel ; outer loop's continue OpBranch %20 ; outer loop's backege %99 = OpLabel OpReturn )"; auto* p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p, *spirv_function(100)); fe.RegisterBasicBlocks(); fe.ComputeBlockOrderAndPositions(); EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 30, 35, 37, 40, 49, 50, 99)); } } // namespace } // namespace spirv } // namespace reader } // namespace tint