// 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 "source/opt/basic_block.h" #include "source/opt/cfg.h" #include "source/opt/dominator_analysis.h" #include "source/opt/function.h" #include "source/opt/instruction.h" // Computes the control dependence graph (CDG) using the algorithm in Cytron // 1991, "Efficiently Computing Static Single Assignment Form and the Control // Dependence Graph." It relies on the fact that the control dependence sources // (blocks on which a block is control dependent) are exactly the post-dominance // frontier for that block. The explanation and proofs are given in Section 6 of // that paper. // Link: https://www.cs.utexas.edu/~pingali/CS380C/2010/papers/ssaCytron.pdf // // The algorithm in Section 4.2 of the same paper is used to construct the // dominance frontier. It uses the post-dominance tree, which is available in // the IR context. namespace spvtools { namespace opt { constexpr uint32_t ControlDependenceAnalysis::kPseudoEntryBlock; uint32_t ControlDependence::GetConditionID(const CFG& cfg) const { if (source_bb_id() == 0) { // Entry dependence; return 0. return 0; } const BasicBlock* source_bb = cfg.block(source_bb_id()); const Instruction* branch = source_bb->terminator(); assert((branch->opcode() == spv::Op::OpBranchConditional || branch->opcode() == spv::Op::OpSwitch) && "invalid control dependence; last instruction must be conditional " "branch or switch"); return branch->GetSingleWordInOperand(0); } bool ControlDependence::operator<(const ControlDependence& other) const { return std::tie(source_bb_id_, target_bb_id_, branch_target_bb_id_) < std::tie(other.source_bb_id_, other.target_bb_id_, other.branch_target_bb_id_); } bool ControlDependence::operator==(const ControlDependence& other) const { return std::tie(source_bb_id_, target_bb_id_, branch_target_bb_id_) == std::tie(other.source_bb_id_, other.target_bb_id_, other.branch_target_bb_id_); } std::ostream& operator<<(std::ostream& os, const ControlDependence& dep) { os << dep.source_bb_id() << "->" << dep.target_bb_id(); if (dep.branch_target_bb_id() != dep.target_bb_id()) { os << " through " << dep.branch_target_bb_id(); } return os; } void ControlDependenceAnalysis::ComputePostDominanceFrontiers( const CFG& cfg, const PostDominatorAnalysis& pdom) { // Compute post-dominance frontiers (reverse graph). // The dominance frontier for a block X is equal to (Equation 4) // DF_local(X) U { B in DF_up(Z) | X = ipdom(Z) } // (ipdom(Z) is the immediate post-dominator of Z.) // where // DF_local(X) = { Y | X -> Y in CFG, X does not strictly post-dominate Y } // represents the contribution of X's predecessors to the DF, and // DF_up(Z) = { Y | Y in DF(Z), ipdom(Z) does not strictly post-dominate Y } // (note: ipdom(Z) = X.) // represents the contribution of a block to its immediate post- // dominator's DF. // This is computed in one pass through a post-order traversal of the // post-dominator tree. // Assert that there is a block other than the pseudo exit in the pdom tree, // as we need one to get the function entry point (as the pseudo exit is not // actually part of the function.) assert(!cfg.IsPseudoExitBlock(pdom.GetDomTree().post_begin()->bb_)); Function* function = pdom.GetDomTree().post_begin()->bb_->GetParent(); uint32_t function_entry = function->entry()->id(); // Explicitly initialize pseudo-entry block, as it doesn't depend on anything, // so it won't be initialized in the following loop. reverse_nodes_[kPseudoEntryBlock] = {}; for (auto it = pdom.GetDomTree().post_cbegin(); it != pdom.GetDomTree().post_cend(); ++it) { ComputePostDominanceFrontierForNode(cfg, pdom, function_entry, *it); } } void ControlDependenceAnalysis::ComputePostDominanceFrontierForNode( const CFG& cfg, const PostDominatorAnalysis& pdom, uint32_t function_entry, const DominatorTreeNode& pdom_node) { const uint32_t label = pdom_node.id(); ControlDependenceList& edges = reverse_nodes_[label]; for (uint32_t pred : cfg.preds(label)) { if (!pdom.StrictlyDominates(label, pred)) { edges.push_back(ControlDependence(pred, label)); } } if (label == function_entry) { // Add edge from pseudo-entry to entry. // In CDG construction, an edge is added from entry to exit, so only the // exit node can post-dominate entry. edges.push_back(ControlDependence(kPseudoEntryBlock, label)); } for (DominatorTreeNode* child : pdom_node) { // Note: iterate dependences by value, as we need a copy. for (const ControlDependence& dep : reverse_nodes_[child->id()]) { // Special-case pseudo-entry, as above. if (dep.source_bb_id() == kPseudoEntryBlock || !pdom.StrictlyDominates(label, dep.source_bb_id())) { edges.push_back(ControlDependence(dep.source_bb_id(), label, dep.branch_target_bb_id())); } } } } void ControlDependenceAnalysis::ComputeControlDependenceGraph( const CFG& cfg, const PostDominatorAnalysis& pdom) { ComputePostDominanceFrontiers(cfg, pdom); ComputeForwardGraphFromReverse(); } void ControlDependenceAnalysis::ComputeForwardGraphFromReverse() { for (const auto& entry : reverse_nodes_) { // Ensure an entry is created for each node. forward_nodes_[entry.first]; for (const ControlDependence& dep : entry.second) { forward_nodes_[dep.source_bb_id()].push_back(dep); } } } } // namespace opt } // namespace spvtools