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