// Copyright (c) 2021 Google LLC. // // 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 "source/opt/control_dependence.h" #include #include #include "gmock/gmock-matchers.h" #include "gtest/gtest.h" #include "source/opt/build_module.h" #include "source/opt/cfg.h" #include "test/opt/function_utils.h" namespace spvtools { namespace opt { namespace { void GatherEdges(const ControlDependenceAnalysis& cdg, std::vector& ret) { cdg.ForEachBlockLabel([&](uint32_t label) { ret.reserve(ret.size() + cdg.GetDependenceTargets(label).size()); ret.insert(ret.end(), cdg.GetDependenceTargets(label).begin(), cdg.GetDependenceTargets(label).end()); }); std::sort(ret.begin(), ret.end()); // Verify that reverse graph is the same. std::vector reverse_edges; reverse_edges.reserve(ret.size()); cdg.ForEachBlockLabel([&](uint32_t label) { reverse_edges.insert(reverse_edges.end(), cdg.GetDependenceSources(label).begin(), cdg.GetDependenceSources(label).end()); }); std::sort(reverse_edges.begin(), reverse_edges.end()); ASSERT_THAT(reverse_edges, testing::ElementsAreArray(ret)); } using ControlDependenceTest = ::testing::Test; TEST(ControlDependenceTest, DependenceSimpleCFG) { const std::string text = R"( OpCapability Addresses OpCapability Kernel OpMemoryModel Physical64 OpenCL OpEntryPoint Kernel %1 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %4 = OpTypeBool %5 = OpTypeInt 32 0 %6 = OpConstant %5 0 %7 = OpConstantFalse %4 %8 = OpConstantTrue %4 %9 = OpConstant %5 1 %1 = OpFunction %2 None %3 %10 = OpLabel OpBranch %11 %11 = OpLabel OpSwitch %6 %12 1 %13 %12 = OpLabel OpBranch %14 %13 = OpLabel OpBranch %14 %14 = OpLabel OpBranchConditional %8 %15 %16 %15 = OpLabel OpBranch %19 %16 = OpLabel OpBranchConditional %8 %17 %18 %17 = OpLabel OpBranch %18 %18 = OpLabel OpBranch %19 %19 = OpLabel OpReturn OpFunctionEnd )"; // CFG: (all edges pointing downward) // %10 // | // %11 // / \ (R: %6 == 1, L: default) // %12 %13 // \ / // %14 // T/ \F // %15 %16 // | T/ |F // | %17| // | \ | // | %18 // | / // %19 std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" << text << std::endl; const Function* fn = spvtest::GetFunction(module, 1); const BasicBlock* entry = spvtest::GetBasicBlock(fn, 10); EXPECT_EQ(entry, fn->entry().get()) << "The entry node is not the expected one"; { PostDominatorAnalysis pdom; const CFG& cfg = *context->cfg(); pdom.InitializeTree(cfg, fn); ControlDependenceAnalysis cdg; cdg.ComputeControlDependenceGraph(cfg, pdom); // Test HasBlock. for (uint32_t id = 10; id <= 19; id++) { EXPECT_TRUE(cdg.HasBlock(id)); } EXPECT_TRUE(cdg.HasBlock(ControlDependenceAnalysis::kPseudoEntryBlock)); // Check blocks before/after valid range. EXPECT_FALSE(cdg.HasBlock(5)); EXPECT_FALSE(cdg.HasBlock(25)); EXPECT_FALSE(cdg.HasBlock(UINT32_MAX)); // Test ForEachBlockLabel. std::set block_labels; cdg.ForEachBlockLabel([&block_labels](uint32_t id) { bool inserted = block_labels.insert(id).second; EXPECT_TRUE(inserted); // Should have no duplicates. }); EXPECT_THAT(block_labels, testing::ElementsAre(0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)); { // Test WhileEachBlockLabel. uint32_t iters = 0; EXPECT_TRUE(cdg.WhileEachBlockLabel([&iters](uint32_t) { ++iters; return true; })); EXPECT_EQ((uint32_t)block_labels.size(), iters); iters = 0; EXPECT_FALSE(cdg.WhileEachBlockLabel([&iters](uint32_t) { ++iters; return false; })); EXPECT_EQ(1, iters); } // Test IsDependent. EXPECT_TRUE(cdg.IsDependent(12, 11)); EXPECT_TRUE(cdg.IsDependent(13, 11)); EXPECT_TRUE(cdg.IsDependent(15, 14)); EXPECT_TRUE(cdg.IsDependent(16, 14)); EXPECT_TRUE(cdg.IsDependent(18, 14)); EXPECT_TRUE(cdg.IsDependent(17, 16)); EXPECT_TRUE(cdg.IsDependent(10, 0)); EXPECT_TRUE(cdg.IsDependent(11, 0)); EXPECT_TRUE(cdg.IsDependent(14, 0)); EXPECT_TRUE(cdg.IsDependent(19, 0)); EXPECT_FALSE(cdg.IsDependent(14, 11)); EXPECT_FALSE(cdg.IsDependent(17, 14)); EXPECT_FALSE(cdg.IsDependent(19, 14)); EXPECT_FALSE(cdg.IsDependent(12, 0)); // Test GetDependenceSources/Targets. std::vector edges; GatherEdges(cdg, edges); EXPECT_THAT(edges, testing::ElementsAre( ControlDependence(0, 10), ControlDependence(0, 11, 10), ControlDependence(0, 14, 10), ControlDependence(0, 19, 10), ControlDependence(11, 12), ControlDependence(11, 13), ControlDependence(14, 15), ControlDependence(14, 16), ControlDependence(14, 18, 16), ControlDependence(16, 17))); const uint32_t expected_condition_ids[] = { 0, 0, 0, 0, 6, 6, 8, 8, 8, 8, }; for (uint32_t i = 0; i < edges.size(); i++) { EXPECT_EQ(expected_condition_ids[i], edges[i].GetConditionID(cfg)); } } } TEST(ControlDependenceTest, DependencePaperCFG) { const std::string text = R"( OpCapability Addresses OpCapability Kernel OpMemoryModel Physical64 OpenCL OpEntryPoint Kernel %101 "main" %102 = OpTypeVoid %103 = OpTypeFunction %102 %104 = OpTypeBool %108 = OpConstantTrue %104 %101 = OpFunction %102 None %103 %1 = OpLabel OpBranch %2 %2 = OpLabel OpBranchConditional %108 %3 %7 %3 = OpLabel OpBranchConditional %108 %4 %5 %4 = OpLabel OpBranch %6 %5 = OpLabel OpBranch %6 %6 = OpLabel OpBranch %8 %7 = OpLabel OpBranch %8 %8 = OpLabel OpBranch %9 %9 = OpLabel OpBranchConditional %108 %10 %11 %10 = OpLabel OpBranch %11 %11 = OpLabel OpBranchConditional %108 %12 %9 %12 = OpLabel OpBranchConditional %108 %13 %2 %13 = OpLabel OpReturn OpFunctionEnd )"; // CFG: (edges pointing downward if no arrow) // %1 // | // %2 <----+ // T/ \F | // %3 \ | // T/ \F \ | // %4 %5 %7 | // \ / / | // %6 / | // \ / | // %8 | // | | // %9 <-+ | // T/ | | | // %10 | | | // \ | | | // %11-F+ | // T| | // %12-F---+ // T| // %13 std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" << text << std::endl; const Function* fn = spvtest::GetFunction(module, 101); const BasicBlock* entry = spvtest::GetBasicBlock(fn, 1); EXPECT_EQ(entry, fn->entry().get()) << "The entry node is not the expected one"; { PostDominatorAnalysis pdom; const CFG& cfg = *context->cfg(); pdom.InitializeTree(cfg, fn); ControlDependenceAnalysis cdg; cdg.ComputeControlDependenceGraph(cfg, pdom); std::vector edges; GatherEdges(cdg, edges); EXPECT_THAT( edges, testing::ElementsAre( ControlDependence(0, 1), ControlDependence(0, 2, 1), ControlDependence(0, 8, 1), ControlDependence(0, 9, 1), ControlDependence(0, 11, 1), ControlDependence(0, 12, 1), ControlDependence(0, 13, 1), ControlDependence(2, 3), ControlDependence(2, 6, 3), ControlDependence(2, 7), ControlDependence(3, 4), ControlDependence(3, 5), ControlDependence(9, 10), ControlDependence(11, 9), ControlDependence(11, 11, 9), ControlDependence(12, 2), ControlDependence(12, 8, 2), ControlDependence(12, 9, 2), ControlDependence(12, 11, 2), ControlDependence(12, 12, 2))); const uint32_t expected_condition_ids[] = { 0, 0, 0, 0, 0, 0, 0, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, }; for (uint32_t i = 0; i < edges.size(); i++) { EXPECT_EQ(expected_condition_ids[i], edges[i].GetConditionID(cfg)); } } } } // namespace } // namespace opt } // namespace spvtools