Home | History | Annotate | Download | only in dominators
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ahat.dominators;
     18 
     19 import com.android.ahat.progress.NullProgress;
     20 import com.android.ahat.progress.Progress;
     21 import java.util.ArrayDeque;
     22 import java.util.Arrays;
     23 import java.util.Deque;
     24 import java.util.Queue;
     25 
     26 /**
     27  * Computes the immediate dominators of a directed graph. It can be used with
     28  * any directed graph data structure that implements the
     29  * {@link Dominators.Graph} interface and has some root node with no incoming
     30  * edges.
     31  */
     32 public class Dominators<Node> {
     33   private final Graph<Node> graph;
     34 
     35   private Progress progress = new NullProgress();
     36   private long numNodes = 0;
     37 
     38   /**
     39    * Interface for a directed graph to perform immediate dominators
     40    * computation on.
     41    * The dominators computation can be used with directed graph data
     42    * structures that implement this <code>Graph</code> interface. To use the
     43    * dominators computation on your graph, you must make the following
     44    * functionality available to the dominators computation:
     45    * <ul>
     46    * <li>Efficiently mapping from node to associated internal dominators
     47    *     computation state using the
     48    *     {@link #setDominatorsComputationState setDominatorsComputationState} and
     49    *     {@link #getDominatorsComputationState getDominatorsComputationState} methods.
     50    * <li>Iterating over all outgoing edges of an node using the
     51    *     {@link #getReferencesForDominators getReferencesForDominators} method.
     52    * <li>Setting the computed dominator for a node using the
     53    *     {@link #setDominator setDominator} method.
     54    * </ul>
     55    */
     56   public interface Graph<Node> {
     57     /**
     58      * Associates the given dominator state with the given node. Subsequent
     59      * calls to
     60      * {@link #getDominatorsComputationState getDominatorsComputationState} on
     61      * this node should return the state given here. At the conclusion of the
     62      * dominators computation, this method will be called for
     63      * each node with <code>state</code> set to null.
     64      *
     65      * @param node the node to associate dominator state
     66      * @param state the dominator state to associate with the node
     67      */
     68     void setDominatorsComputationState(Node node, Object state);
     69 
     70     /**
     71      * Returns the dominator state most recently associated with the given node
     72      * by a call to {@link #setDominatorsComputationState setDominatorsComputationState}.
     73      * If <code>setDominatorsComputationState</code> has not yet been called
     74      * on this node for this dominators computation, this method should return
     75      * null.
     76      *
     77      * @param node the node to get the dominator state for
     78      * @return the associated dominator state
     79      */
     80     Object getDominatorsComputationState(Node node);
     81 
     82     /**
     83      * Returns a collection of nodes referenced from the given node, for the
     84      * purposes of computing dominators. This method will be called at most
     85      * once for each node reachable from the root node of the dominators
     86      * computation.
     87      *
     88      * @param node the node to get the references for
     89      * @return an iterable collection of the nodes with an incoming edge from
     90      *         the node.
     91      */
     92     Iterable<? extends Node> getReferencesForDominators(Node node);
     93 
     94     /**
     95      * Sets the dominator for the given node based on the results of the
     96      * dominators computation.
     97      *
     98      * @param node the node to set the dominator for
     99      * @param dominator the computed immediate dominator of the node
    100      */
    101     void setDominator(Node node, Node dominator);
    102   }
    103 
    104   /**
    105    * Construct an object to do dominators computation on the given graph.
    106    *
    107    * @param graph the graph to compute the dominators of
    108    */
    109   public Dominators(Graph graph) {
    110     this.graph = graph;
    111   }
    112 
    113   /**
    114    * Sets up a progress tracker for the dominators computation.
    115    *
    116    * @param progress the progress tracker to use
    117    * @param numNodes an upper bound on the number of nodes in the graph
    118    * @return this Dominators object
    119    */
    120   public Dominators progress(Progress progress, long numNodes) {
    121     this.progress = progress;
    122     this.numNodes = numNodes;
    123     return this;
    124   }
    125 
    126   // NodeS is information associated with a particular node for the
    127   // purposes of computing dominators.
    128   // By convention we use the suffix 'S' to name instances of NodeS.
    129   private static class NodeS {
    130     // The node that this NodeS is associated with.
    131     public Object node;
    132 
    133     // Unique identifier for this node, in increasing order based on the order
    134     // this node was visited in a depth first search from the root. In
    135     // particular, given nodes A and B, if A.id > B.id, then A cannot be a
    136     // dominator of B.
    137     public long id;
    138 
    139     // The largest id of all nodes reachable from this node.
    140     // If foo.id > this.maxReachableId, then foo is not reachable from this
    141     // node.
    142     public long maxReachableId;
    143 
    144     // The set of ids of nodes that have references to this node.
    145     public IdSet inRefIds = new IdSet();
    146 
    147     // The current candidate dominator for this node.
    148     // The true immediate dominator of this node must have id <= domS.id.
    149     public NodeS domS;
    150 
    151     // The previous candidate dominator for this node.
    152     // Invariant:
    153     // * There are no nodes xS reachable from this node on a path of nodes
    154     //   with increasing ids (not counting xS.id) for which
    155     //   this.id > xS.domS.id > this.oldDomS.id.
    156     // This ensures that when all nodes xS satisfy xS.domS == xS.oldDomS, we
    157     // have found the true immediate dominator of each node.
    158     //
    159     // Note: We only use this field to tell if this node is scheduled to be
    160     // revisited. We could replace it with a boolean to save space, but it
    161     // probably doesn't save that much space and it's easier to explain the
    162     // algorithm if we can refer to this field.
    163     public NodeS oldDomS;
    164 
    165     // The set of nodes that this node is the candidate immediate dominator
    166     // of. More precisely, the set of nodes xS such that xS.domS == this.
    167     public NodeSet dominated = new NodeSet();
    168 
    169     // The set of nodes that this node is the old candidate immediate
    170     // dominator of that need to be revisited. Specifically, the set of nodes
    171     // xS such that:
    172     //   xS.oldDomS == this && xS.oldDomS != xS.domS.
    173     //
    174     // The empty set is represented as null instead of an empty NodeSet to
    175     // save memory.
    176     // Invariant:
    177     //   If revisit != null, this node is on the global list of nodes to be
    178     //   revisited.
    179     public NodeSet revisit = null;
    180 
    181     // Distance from the root to this node. Used for purposes of tracking
    182     // progress only.
    183     public long depth;
    184   }
    185 
    186   // A collection of node ids.
    187   private static class IdSet {
    188     private int size = 0;
    189     private long[] ids = new long[4];
    190 
    191     // Adds an id to the set.
    192     public void add(long id) {
    193       if (size == ids.length) {
    194         ids = Arrays.copyOf(ids, size * 2);
    195       }
    196       ids[size++] = id;
    197     }
    198 
    199     // Returns the most recent id added to the set. Behavior is undefined if
    200     // the set is empty.
    201     public long last() {
    202       assert size != 0;
    203       return ids[size - 1];
    204     }
    205 
    206     // Returns true if the set contains an id in the range [low, high]
    207     // inclusive, false otherwise.
    208     public boolean hasIdInRange(long low, long high) {
    209       for (int i = 0; i < size; ++i) {
    210         if (low <= ids[i] && ids[i] <= high) {
    211           return true;
    212         }
    213       }
    214       return false;
    215     }
    216   }
    217 
    218   // An unordered set of nodes data structure supporting efficient iteration
    219   // over elements. The bulk of the time spent in the dominators algorithm is
    220   // iterating over these sets. Using an array to store the set provides
    221   // noticable performance improvements over ArrayList or a linked list.
    222   private static class NodeSet {
    223     public int size = 0;
    224     public NodeS[] nodes = new NodeS[4];
    225 
    226     public void add(NodeS nodeS) {
    227       if (size == nodes.length) {
    228         nodes = Arrays.copyOf(nodes, size * 2);
    229       }
    230       nodes[size++] = nodeS;
    231     }
    232 
    233     public void remove(NodeS nodeS) {
    234       for (int i = 0; i < size; ++i) {
    235         if (nodes[i] == nodeS) {
    236           remove(i);
    237           break;
    238         }
    239       }
    240     }
    241 
    242     public void remove(int index) {
    243       nodes[index] = nodes[--size];
    244       nodes[size] = null;
    245     }
    246   }
    247 
    248   // A reference from a source node to a destination node to be processed
    249   // during the initial depth-first traversal of nodes.
    250   //
    251   // Also used as a marker to indicate when the depth-first traversal has been
    252   // completed for a node. In that case, srcS is the node depth-first
    253   // traversal has been completed for, and dst will be set to null.
    254   private static class Link<Node> {
    255     public final NodeS srcS;
    256     public final Node dst;
    257 
    258     // Constructor for a reference from srcS to dst.
    259     public Link(NodeS srcS, Node dst) {
    260       this.srcS = srcS;
    261       this.dst = dst;
    262     }
    263 
    264     // Constructor for a marker indicating depth-first traversal has been
    265     // completed for srcS.
    266     public Link(NodeS srcS) {
    267       this.srcS = srcS;
    268       this.dst = null;
    269     }
    270   }
    271 
    272   /**
    273    * Computes the immediate dominators of all nodes reachable from the <code>root</code> node.
    274    * There must not be any incoming references to the <code>root</code> node.
    275    * <p>
    276    * The result of this function is to call the {@link Graph#setDominator}
    277    * function on every node reachable from the root node.
    278    *
    279    * @param root the root node of the dominators computation
    280    */
    281   public void computeDominators(Node root) {
    282     long id = 0;
    283 
    284     // The set of nodes xS such that xS.revisit != null.
    285     // Use a Queue instead of a Set because performance will be better. We
    286     // avoid adding nodes already on the queue by checking
    287     // xS == null before adding the node to the queue.
    288     Queue<NodeS> revisit = new ArrayDeque<NodeS>();
    289 
    290     // Set up the root node specially.
    291     NodeS rootS = new NodeS();
    292     rootS.node = root;
    293     rootS.id = id++;
    294     rootS.depth = 0;
    295     graph.setDominatorsComputationState(root, rootS);
    296 
    297     Deque<Link<Node>> dfs = new ArrayDeque<Link<Node>>();
    298     dfs.push(new Link(rootS));
    299     for (Node child : graph.getReferencesForDominators(root)) {
    300       dfs.push(new Link(rootS, child));
    301     }
    302 
    303     // workBound is an upper bound on the amount of work required in the
    304     // second phase of dominators computation, used solely for the purposes of
    305     // tracking progress.
    306     long workBound = 0;
    307 
    308     // 1. Do a depth first search of the nodes, label them with ids and come
    309     // up with initial candidate dominators for them.
    310     progress.start("Initializing dominators", numNodes);
    311     while (!dfs.isEmpty()) {
    312       Link<Node> link = dfs.pop();
    313 
    314       if (link.dst == null) {
    315         // This is the marker link indicating we have now visited all
    316         // nodes reachable from link.srcS.
    317         link.srcS.maxReachableId = id - 1;
    318         progress.advance();
    319       } else {
    320         NodeS dstS = (NodeS)graph.getDominatorsComputationState(link.dst);
    321         if (dstS == null) {
    322           // We are seeing the destination node for the first time.
    323           // The candidate dominator is the source node.
    324           dstS = new NodeS();
    325           graph.setDominatorsComputationState(link.dst, dstS);
    326 
    327           dstS.node = link.dst;
    328           dstS.id = id++;
    329           dstS.inRefIds.add(link.srcS.id);
    330           dstS.domS = link.srcS;
    331           dstS.domS.dominated.add(dstS);
    332           dstS.oldDomS = link.srcS;
    333           dstS.depth = link.srcS.depth + 1;
    334 
    335           dfs.push(new Link<>(dstS));
    336           for (Node child : graph.getReferencesForDominators(link.dst)) {
    337             dfs.push(new Link<>(dstS, child));
    338           }
    339         } else {
    340           // We have seen the destination node before. Update the state based
    341           // on the new potential dominator.
    342           if (dstS.inRefIds.size == 1) {
    343             workBound += dstS.oldDomS.depth;
    344           }
    345 
    346           long seenid = dstS.inRefIds.last();
    347           dstS.inRefIds.add(link.srcS.id);
    348 
    349           // Go up the dominator chain until we reach a node we haven't already
    350           // seen with a path to dstS.
    351           NodeS xS = link.srcS;
    352           while (xS.id > seenid) {
    353             xS = xS.domS;
    354           }
    355 
    356           // The new dominator for dstS must have an id less than the node we
    357           // just reached. Pull the dominator for dstS up its dominator
    358           // chain until we find a suitable new dominator for dstS.
    359           long domid = xS.id;
    360           if (dstS.domS.id > domid) {
    361             // Mark the node as needing to be revisited.
    362             if (dstS.domS == dstS.oldDomS) {
    363               if (dstS.oldDomS.revisit == null) {
    364                 dstS.oldDomS.revisit = new NodeSet();
    365                 revisit.add(dstS.oldDomS);
    366               }
    367               dstS.oldDomS.revisit.add(dstS);
    368             }
    369 
    370             // Update the node's candidate dominator.
    371             dstS.domS.dominated.remove(dstS);
    372             do {
    373               dstS.domS = dstS.domS.domS;
    374             } while (dstS.domS.id > domid);
    375             dstS.domS.dominated.add(dstS);
    376           }
    377         }
    378       }
    379     }
    380     progress.done();
    381 
    382     // 2. Continue revisiting nodes until every node satisfies the requirement
    383     // that domS.id == oldDomS.id.
    384     progress.start("Resolving dominators", workBound);
    385     while (!revisit.isEmpty()) {
    386       NodeS oldDomS = revisit.poll();
    387       assert oldDomS.revisit != null;
    388 
    389       NodeSet nodes = oldDomS.revisit;
    390       oldDomS.revisit = null;
    391 
    392       // Search for pairs of nodes nodeS, xS for which
    393       //    nodeS.id > xS.domS.id > nodeS.oldDomS.id
    394       // and there is a path of nodes with increasing ids from nodeS to xS.
    395       // In that case, xS.domS must be wrong, because there is a path to xS
    396       // from the root that does not go through xS.domS:
    397       // * There is a path from the root to nodeS.oldDomS that doesn't go
    398       //   through xS.domS. Otherwise xS.domS would be a dominator of
    399       //   nodeS.oldDomS, but it can't be because xS.domS.id > nodeS.oldDomS.id.
    400       // * There is a path from nodeS.oldDomS to nodeS that doesn't go through
    401       //   xS.domS, because xS.domS is not a dominator of nodeS.
    402       // * There is a path from nodeS to xS that doesn't go through xS.domS,
    403       //   because we have a path of increasing ids from nodeS to xS, none of
    404       //   which can have an id smaller than nodeS as xS.domS does.
    405       for (int i = 0; i < oldDomS.dominated.size; ++i) {
    406         NodeS xS = oldDomS.dominated.nodes[i];
    407         for (int j = 0; j < nodes.size; ++j) {
    408           NodeS nodeS = nodes.nodes[j];
    409           assert nodeS.oldDomS == oldDomS;
    410           if (isReachableAscending(nodeS, xS)) {
    411             // Update the dominator for xS.
    412             if (xS.domS == xS.oldDomS) {
    413               if (xS.oldDomS.revisit == null) {
    414                 xS.oldDomS.revisit = new NodeSet();
    415                 revisit.add(xS.oldDomS);
    416               }
    417               xS.oldDomS.revisit.add(xS);
    418             }
    419             oldDomS.dominated.remove(i--);
    420             xS.domS = nodeS.domS;
    421             xS.domS.dominated.add(xS);
    422             break;
    423           }
    424         }
    425       }
    426 
    427       // We can now safely update oldDomS for each of the nodes nodeS while
    428       // preserving the oldDomS invariant.
    429       for (int i = 0; i < nodes.size; ++i) {
    430         NodeS nodeS = nodes.nodes[i];
    431         nodeS.oldDomS = oldDomS.oldDomS;
    432         if (nodeS.oldDomS != nodeS.domS) {
    433           if (nodeS.oldDomS.revisit == null) {
    434             nodeS.oldDomS.revisit = new NodeSet();
    435             revisit.add(nodeS.oldDomS);
    436           }
    437           nodeS.oldDomS.revisit.add(nodeS);
    438         }
    439       }
    440       progress.advance((oldDomS.depth - oldDomS.oldDomS.depth) * nodes.size);
    441     }
    442     progress.done();
    443 
    444 
    445     // 3. We have figured out the correct dominator for each node. Notify the
    446     // user of the results by doing one last traversal of the nodes.
    447     assert revisit.isEmpty();
    448     revisit.add(rootS);
    449     while (!revisit.isEmpty()) {
    450       NodeS nodeS = revisit.poll();
    451       assert nodeS.domS == nodeS.oldDomS;
    452       assert nodeS.revisit == null;
    453       graph.setDominatorsComputationState((Node)nodeS.node, null);
    454       for (int i = 0; i < nodeS.dominated.size; ++i) {
    455         NodeS xS = nodeS.dominated.nodes[i];
    456         graph.setDominator((Node)xS.node, (Node)nodeS.node);
    457         revisit.add(xS);
    458       }
    459     }
    460   }
    461 
    462   // Returns true if there is a path from srcS to dstS of nodes with ascending
    463   // ids (not including dstS.id).
    464   private static boolean isReachableAscending(NodeS srcS, NodeS dstS) {
    465     if (dstS.id < srcS.id) {
    466       // The first time we saw dstS was before we saw srcS. See if srcS is on
    467       // the source chain for any nodes with direct references to dstS.
    468       return dstS.inRefIds.hasIdInRange(srcS.id, srcS.maxReachableId);
    469     }
    470 
    471     // Otherwise dstS is only reachable from srcS on a node with ascending ids
    472     // if it was visited for the first time while performing the depth-first
    473     // traversal of srcS.
    474     return dstS.id <= srcS.maxReachableId;
    475   }
    476 }
    477