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