| // 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 <algorithm> |
| #include <vector> |
| |
| #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<ControlDependence>& 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<ControlDependence> 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<IRContext> 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<uint32_t> 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<ControlDependence> 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<IRContext> 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<ControlDependence> 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 |