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 */ setDominatorsComputationState(Node node, Object state)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 */ getDominatorsComputationState(Node node)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 */ getReferencesForDominators(Node node)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 */ setDominator(Node node, Node dominator)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 */ Dominators(Graph graph)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 */ progress(Progress progress, long numNodes)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. add(long id)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. last()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. hasIdInRange(long low, long high)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 add(NodeS nodeS)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 remove(NodeS nodeS)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 remove(int index)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. Link(NodeS srcS, Node 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. Link(NodeS 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 */ computeDominators(Node root)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). isReachableAscending(NodeS srcS, NodeS dstS)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