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 java.util.ArrayDeque; 20 import java.util.ArrayList; 21 import java.util.Deque; 22 import java.util.List; 23 import java.util.Queue; 24 25 /** 26 * Provides a static method for computing the immediate dominators of a 27 * directed graph. It can be used with any directed graph data structure 28 * that implements the {@link DominatorsComputation.Node} interface and has 29 * some root node with no incoming edges. 30 */ 31 public class DominatorsComputation { DominatorsComputation()32 private DominatorsComputation() { 33 } 34 35 /** 36 * Interface for a directed graph to perform immediate dominators 37 * computation on. 38 * The dominators computation can be used with directed graph data 39 * structures that implement this <code>Node</code> interface. To use the 40 * dominators computation on your graph, you must make the following 41 * functionality available to the dominators computation: 42 * <ul> 43 * <li>Efficiently mapping from node to associated internal dominators 44 * computation state using the 45 * {@link #setDominatorsComputationState setDominatorsComputationState} and 46 * {@link #getDominatorsComputationState getDominatorsComputationState} methods. 47 * <li>Iterating over all outgoing edges of an node using the 48 * {@link #getReferencesForDominators getReferencesForDominators} method. 49 * <li>Setting the computed dominator for a node using the 50 * {@link #setDominator setDominator} method. 51 * </ul> 52 */ 53 public interface Node { 54 /** 55 * Associates the given dominator state with this node. Subsequent calls to 56 * {@link #getDominatorsComputationState getDominatorsComputationState} on 57 * this node should return the state given here. At the conclusion of the 58 * dominators computation, this method will be called for 59 * each node with <code>state</code> set to null. 60 * 61 * @param state the dominator state to associate with this node 62 */ setDominatorsComputationState(Object state)63 void setDominatorsComputationState(Object state); 64 65 /** 66 * Returns the dominator state most recently associated with this node 67 * by a call to {@link #setDominatorsComputationState setDominatorsComputationState}. 68 * If <code>setDominatorsComputationState</code> has not yet been called 69 * on this node for this dominators computation, this method should return 70 * null. 71 * 72 * @return the associated dominator state 73 */ getDominatorsComputationState()74 Object getDominatorsComputationState(); 75 76 /** 77 * Returns a collection of nodes referenced from this node, for the 78 * purposes of computing dominators. This method will be called at most 79 * once for each node reachable from the root node of the dominators 80 * computation. 81 * 82 * @return an iterable collection of the nodes with an incoming edge from 83 * this node. 84 */ getReferencesForDominators()85 Iterable<? extends Node> getReferencesForDominators(); 86 87 /** 88 * Sets the dominator for this node based on the results of the dominators 89 * computation. 90 * 91 * @param dominator the computed immediate dominator of this node 92 */ setDominator(Node dominator)93 void setDominator(Node dominator); 94 } 95 96 // NodeS is information associated with a particular node for the 97 // purposes of computing dominators. 98 // By convention we use the suffix 'S' to name instances of NodeS. 99 private static class NodeS { 100 // The node that this NodeS is associated with. 101 public Node node; 102 103 // Unique identifier for this node, in increasing order based on the order 104 // this node was visited in a depth first search from the root. In 105 // particular, given nodes A and B, if A.id > B.id, then A cannot be a 106 // dominator of B. 107 public long id; 108 109 // Upper bound on the id of this node's dominator. 110 // The true immediate dominator of this node must have id <= domid. 111 // This upper bound is slowly tightened as part of the dominators 112 // computation. 113 public long domid; 114 115 // The current candidate dominator for this node. 116 // Invariant: (domid < domS.id) implies this node is on the queue of 117 // nodes to be revisited. 118 public NodeS domS; 119 120 // A node with a reference to this node that is one step closer to the 121 // root than this node. 122 // Invariant: srcS.id < this.id 123 public NodeS srcS; 124 125 // The largest id of the nodes we have seen so far on a path from the root 126 // to this node. Used to keep track of which nodes we have already seen 127 // and avoid processing them again. 128 public long seenid; 129 130 // The set of nodes X reachable by 'this' on a path of nodes from the 131 // root with increasing ids (possibly excluding X) that this node does not 132 // dominate (this.id > X.domid). 133 // We can use a List instead of a Set for this because we guarentee based 134 // on seenid that we don't add the same node more than once to the list. 135 public List<NodeS> undom = new ArrayList<NodeS>(); 136 } 137 138 private static class Link { 139 public NodeS srcS; 140 public Node dst; 141 Link(NodeS srcS, Node dst)142 public Link(NodeS srcS, Node dst) { 143 this.srcS = srcS; 144 this.dst = dst; 145 } 146 } 147 148 /** 149 * Computes the immediate dominators of all nodes reachable from the <code>root</code> node. 150 * There must not be any incoming references to the <code>root</code> node. 151 * <p> 152 * The result of this function is to call the {@link Node#setDominator} 153 * function on every node reachable from the root node. 154 * 155 * @param root the root node of the dominators computation 156 * @see Node 157 */ computeDominators(Node root)158 public static void computeDominators(Node root) { 159 long id = 0; 160 161 // List of all nodes seen. We keep track of this here to update all the 162 // dominators once we are done. 163 List<NodeS> nodes = new ArrayList<NodeS>(); 164 165 // The set of nodes N such that N.domid < N.domS.id. These nodes need 166 // to be revisisted because their dominator is clearly wrong. 167 // Use a Queue instead of a Set because performance will be better. We 168 // avoid adding nodes already on the queue by checking whether it was 169 // already true that N.domid < N.domS.id, in which case the node is 170 // already on the queue. 171 Queue<NodeS> revisit = new ArrayDeque<NodeS>(); 172 173 // Set up the root node specially. 174 NodeS rootS = new NodeS(); 175 rootS.node = root; 176 rootS.id = id++; 177 root.setDominatorsComputationState(rootS); 178 179 // 1. Do a depth first search of the nodes, label them with ids and come 180 // up with intial candidate dominators for them. 181 Deque<Link> dfs = new ArrayDeque<Link>(); 182 for (Node child : root.getReferencesForDominators()) { 183 dfs.push(new Link(rootS, child)); 184 } 185 186 while (!dfs.isEmpty()) { 187 Link link = dfs.pop(); 188 NodeS dstS = (NodeS)link.dst.getDominatorsComputationState(); 189 if (dstS == null) { 190 // This is the first time we have seen the node. The candidate 191 // dominator is link src. 192 dstS = new NodeS(); 193 dstS.node = link.dst; 194 dstS.id = id++; 195 dstS.domid = link.srcS.id; 196 dstS.domS = link.srcS; 197 dstS.srcS = link.srcS; 198 dstS.seenid = dstS.domid; 199 nodes.add(dstS); 200 link.dst.setDominatorsComputationState(dstS); 201 202 for (Node child : link.dst.getReferencesForDominators()) { 203 dfs.push(new Link(dstS, child)); 204 } 205 } else { 206 // We have seen the node already. Update the state based on the new 207 // potential dominator. 208 NodeS srcS = link.srcS; 209 boolean revisiting = dstS.domid < dstS.domS.id; 210 211 while (srcS.id > dstS.seenid) { 212 srcS.undom.add(dstS); 213 srcS = srcS.srcS; 214 } 215 dstS.seenid = link.srcS.id; 216 217 if (srcS.id < dstS.domid) { 218 // In this case, dstS.domid must be wrong, because we just found a 219 // path to dstS that does not go through dstS.domid: 220 // All nodes from root to srcS have id < domid, and all nodes from 221 // srcS to dstS had id > domid, so dstS.domid cannot be on this path 222 // from root to dstS. 223 dstS.domid = srcS.id; 224 if (!revisiting) { 225 revisit.add(dstS); 226 } 227 } 228 } 229 } 230 231 // 2. Continue revisiting nodes until they all satisfy the requirement 232 // that domS.id <= domid. 233 while (!revisit.isEmpty()) { 234 NodeS nodeS = revisit.poll(); 235 NodeS domS = nodeS.domS; 236 assert nodeS.domid < domS.id; 237 while (domS.id > nodeS.domid) { 238 if (domS.domS.id < nodeS.domid) { 239 // In this case, nodeS.domid must be wrong, because there is a path 240 // from root to nodeS that does not go through nodeS.domid: 241 // * We can go from root to domS without going through nodeS.domid, 242 // because otherwise nodeS.domid would dominate domS, not 243 // domS.domS. 244 // * We can go from domS to nodeS without going through nodeS.domid 245 // because we know nodeS is reachable from domS on a path of nodes 246 // with increases ids, which cannot include nodeS.domid, which 247 // has a smaller id than domS. 248 nodeS.domid = domS.domS.id; 249 } 250 domS.undom.add(nodeS); 251 domS = domS.srcS; 252 } 253 nodeS.domS = domS; 254 nodeS.domid = domS.id; 255 256 for (NodeS xS : nodeS.undom) { 257 if (domS.id < xS.domid) { 258 // In this case, xS.domid must be wrong, because there is a path 259 // from the root to xX that does not go through xS.domid: 260 // * We can go from root to nodeS without going through xS.domid, 261 // because otherwise xS.domid would dominate nodeS, not domS. 262 // * We can go from nodeS to xS without going through xS.domid 263 // because we know xS is reachable from nodeS on a path of nodes 264 // with increasing ids, which cannot include xS.domid, which has 265 // a smaller id than nodeS. 266 boolean revisiting = xS.domid < xS.domS.id; 267 xS.domid = domS.id; 268 if (!revisiting) { 269 revisit.add(xS); 270 } 271 } 272 } 273 } 274 275 // 3. Update the dominators of the nodes. 276 root.setDominatorsComputationState(null); 277 for (NodeS nodeS : nodes) { 278 nodeS.node.setDominator(nodeS.domS.node); 279 nodeS.node.setDominatorsComputationState(null); 280 } 281 } 282 } 283