ComputeInliningRelevance.java revision 12651:6ef01bd40ce2
1243730Srwatson/*
2243730Srwatson * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
3243730Srwatson * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4243730Srwatson *
5243730Srwatson * This code is free software; you can redistribute it and/or modify it
6243730Srwatson * under the terms of the GNU General Public License version 2 only, as
7243730Srwatson * published by the Free Software Foundation.
8243730Srwatson *
9243730Srwatson * This code is distributed in the hope that it will be useful, but WITHOUT
10243730Srwatson * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11243730Srwatson * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12243730Srwatson * version 2 for more details (a copy is included in the LICENSE file that
13243730Srwatson * accompanied this code).
14243730Srwatson *
15243730Srwatson * You should have received a copy of the GNU General Public License version
16243730Srwatson * 2 along with this work; if not, write to the Free Software Foundation,
17243730Srwatson * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18243730Srwatson *
19243730Srwatson * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20243730Srwatson * or visit www.oracle.com if you need additional information or have any
21243730Srwatson * questions.
22243730Srwatson */
23243730Srwatsonpackage org.graalvm.compiler.phases.common.inlining.walker;
24243730Srwatson
25243730Srwatsonimport java.util.ArrayList;
26243730Srwatsonimport java.util.Map;
27243730Srwatsonimport java.util.function.ToDoubleFunction;
28243730Srwatson
29243730Srwatsonimport org.graalvm.compiler.core.common.SuppressFBWarnings;
30243730Srwatsonimport org.graalvm.compiler.graph.Node;
31243730Srwatsonimport org.graalvm.compiler.graph.NodeWorkList;
32243730Srwatsonimport org.graalvm.compiler.nodes.AbstractBeginNode;
33243730Srwatsonimport org.graalvm.compiler.nodes.AbstractMergeNode;
34243730Srwatsonimport org.graalvm.compiler.nodes.ControlSinkNode;
35243730Srwatsonimport org.graalvm.compiler.nodes.ControlSplitNode;
36243730Srwatsonimport org.graalvm.compiler.nodes.EndNode;
37243730Srwatsonimport org.graalvm.compiler.nodes.FixedNode;
38243730Srwatsonimport org.graalvm.compiler.nodes.FixedWithNextNode;
39243730Srwatsonimport org.graalvm.compiler.nodes.Invoke;
40243730Srwatsonimport org.graalvm.compiler.nodes.LoopBeginNode;
41243730Srwatsonimport org.graalvm.compiler.nodes.LoopEndNode;
42243730Srwatsonimport org.graalvm.compiler.nodes.LoopExitNode;
43243730Srwatsonimport org.graalvm.compiler.nodes.MergeNode;
44243730Srwatsonimport org.graalvm.compiler.nodes.StartNode;
45243730Srwatsonimport org.graalvm.compiler.nodes.StructuredGraph;
46243730Srwatsonimport org.graalvm.compiler.phases.common.inlining.InliningUtil;
47243730Srwatson
48243730Srwatsonpublic class ComputeInliningRelevance {
49243730Srwatson
50243730Srwatson    private static final double EPSILON = 1d / Integer.MAX_VALUE;
51243730Srwatson    private static final double UNINITIALIZED = -1D;
52243730Srwatson
53243730Srwatson    private static final int EXPECTED_MIN_INVOKE_COUNT = 3;
54    private static final int EXPECTED_INVOKE_RATIO = 20;
55    private static final int EXPECTED_LOOP_COUNT = 3;
56
57    private final StructuredGraph graph;
58    private final ToDoubleFunction<FixedNode> nodeProbabilities;
59
60    /**
61     * Node relevances are pre-computed for all invokes if the graph contains loops. If there are no
62     * loops, the computation happens lazily based on {@link #rootScope}.
63     */
64    private Map<FixedNode, Double> nodeRelevances;
65    /**
66     * This scope is non-null if (and only if) there are no loops in the graph. In this case, the
67     * root scope is used to compute invoke relevances on the fly.
68     */
69    private Scope rootScope;
70
71    public ComputeInliningRelevance(StructuredGraph graph, ToDoubleFunction<FixedNode> nodeProbabilities) {
72        this.graph = graph;
73        this.nodeProbabilities = nodeProbabilities;
74    }
75
76    /**
77     * Initializes or updates the relevance computation. If there are no loops within the graph,
78     * most computation happens lazily.
79     */
80    public void compute() {
81        rootScope = null;
82        if (!graph.hasLoops()) {
83            // fast path for the frequent case of no loops
84            rootScope = new Scope(graph.start(), null);
85        } else {
86            if (nodeRelevances == null) {
87                nodeRelevances = Node.newIdentityMap(EXPECTED_MIN_INVOKE_COUNT + InliningUtil.getNodeCount(graph) / EXPECTED_INVOKE_RATIO);
88            }
89            NodeWorkList workList = graph.createNodeWorkList();
90            Map<LoopBeginNode, Scope> loops = Node.newIdentityMap(EXPECTED_LOOP_COUNT);
91
92            loops.put(null, new Scope(graph.start(), null));
93            for (LoopBeginNode loopBegin : graph.getNodes(LoopBeginNode.TYPE)) {
94                createLoopScope(loopBegin, loops);
95            }
96
97            for (Scope scope : loops.values()) {
98                scope.process(workList);
99            }
100        }
101    }
102
103    public double getRelevance(Invoke invoke) {
104        if (rootScope != null) {
105            return rootScope.computeInvokeRelevance(invoke);
106        }
107        assert nodeRelevances != null : "uninitialized relevance";
108        return nodeRelevances.get(invoke);
109    }
110
111    /**
112     * Determines the parent of the given loop and creates a {@link Scope} object for each one. This
113     * method will call itself recursively if no {@link Scope} for the parent loop exists.
114     */
115    private Scope createLoopScope(LoopBeginNode loopBegin, Map<LoopBeginNode, Scope> loops) {
116        Scope scope = loops.get(loopBegin);
117        if (scope == null) {
118            final Scope parent;
119            // look for the parent scope
120            FixedNode current = loopBegin.forwardEnd();
121            while (true) {
122                if (current.predecessor() == null) {
123                    if (current instanceof LoopBeginNode) {
124                        // if we reach a LoopBeginNode then we're within this loop
125                        parent = createLoopScope((LoopBeginNode) current, loops);
126                        break;
127                    } else if (current instanceof StartNode) {
128                        // we're within the outermost scope
129                        parent = loops.get(null);
130                        break;
131                    } else {
132                        assert current instanceof MergeNode : current;
133                        // follow any path upwards - it doesn't matter which one
134                        current = ((AbstractMergeNode) current).forwardEndAt(0);
135                    }
136                } else if (current instanceof LoopExitNode) {
137                    // if we reach a loop exit then we follow this loop and have the same parent
138                    parent = createLoopScope(((LoopExitNode) current).loopBegin(), loops).parent;
139                    break;
140                } else {
141                    current = (FixedNode) current.predecessor();
142                }
143            }
144            scope = new Scope(loopBegin, parent);
145            loops.put(loopBegin, scope);
146        }
147        return scope;
148    }
149
150    /**
151     * A scope holds information for the contents of one loop or of the root of the method. It does
152     * not include child loops, i.e., the iteration in {@link #process(NodeWorkList)} explicitly
153     * excludes the nodes of child loops.
154     */
155    private class Scope {
156        public final FixedNode start;
157        public final Scope parent; // can be null for the outermost scope
158
159        /**
160         * The minimum probability along the most probable path in this scope. Computed lazily.
161         */
162        private double fastPathMinProbability = UNINITIALIZED;
163        /**
164         * A measure of how important this scope is within its parent scope. Computed lazily.
165         */
166        private double scopeRelevanceWithinParent = UNINITIALIZED;
167
168        Scope(FixedNode start, Scope parent) {
169            this.start = start;
170            this.parent = parent;
171        }
172
173        @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY", justification = "comparing against -1D is accurate")
174        public double getFastPathMinProbability() {
175            if (fastPathMinProbability == UNINITIALIZED) {
176                fastPathMinProbability = Math.max(EPSILON, computeFastPathMinProbability(start));
177            }
178            return fastPathMinProbability;
179        }
180
181        /**
182         * Computes the ratio between the probabilities of the current scope's entry point and the
183         * parent scope's fastPathMinProbability.
184         */
185        @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY", justification = "comparing against -1D is accurate")
186        public double getScopeRelevanceWithinParent() {
187            if (scopeRelevanceWithinParent == UNINITIALIZED) {
188                if (start instanceof LoopBeginNode) {
189                    assert parent != null;
190                    double scopeEntryProbability = nodeProbabilities.applyAsDouble(((LoopBeginNode) start).forwardEnd());
191
192                    scopeRelevanceWithinParent = scopeEntryProbability / parent.getFastPathMinProbability();
193                } else {
194                    scopeRelevanceWithinParent = 1D;
195                }
196            }
197            return scopeRelevanceWithinParent;
198        }
199
200        /**
201         * Processes all invokes in this scope by starting at the scope's start node and iterating
202         * all fixed nodes. Child loops are skipped by going from loop entries directly to the loop
203         * exits. Processing stops at loop exits of the current loop.
204         */
205        public void process(NodeWorkList workList) {
206            assert !(start instanceof Invoke);
207            workList.addAll(start.successors());
208
209            for (Node current : workList) {
210                assert current.isAlive();
211
212                if (current instanceof Invoke) {
213                    // process the invoke and queue its successors
214                    nodeRelevances.put((FixedNode) current, computeInvokeRelevance((Invoke) current));
215                    workList.addAll(current.successors());
216                } else if (current instanceof LoopBeginNode) {
217                    // skip child loops by advancing over the loop exits
218                    ((LoopBeginNode) current).loopExits().forEach(exit -> workList.add(exit.next()));
219                } else if (current instanceof LoopEndNode) {
220                    // nothing to do
221                } else if (current instanceof LoopExitNode) {
222                    // nothing to do
223                } else if (current instanceof FixedWithNextNode) {
224                    workList.add(((FixedWithNextNode) current).next());
225                } else if (current instanceof EndNode) {
226                    workList.add(((EndNode) current).merge());
227                } else if (current instanceof ControlSinkNode) {
228                    // nothing to do
229                } else if (current instanceof ControlSplitNode) {
230                    workList.addAll(current.successors());
231                } else {
232                    assert false : current;
233                }
234            }
235        }
236
237        /**
238         * The relevance of an invoke is the ratio between the invoke's probability and the current
239         * scope's fastPathMinProbability, adjusted by scopeRelevanceWithinParent.
240         */
241        public double computeInvokeRelevance(Invoke invoke) {
242            double invokeProbability = nodeProbabilities.applyAsDouble(invoke.asNode());
243            assert !Double.isNaN(invokeProbability);
244
245            double relevance = (invokeProbability / getFastPathMinProbability()) * Math.min(1.0, getScopeRelevanceWithinParent());
246            assert !Double.isNaN(relevance) : invoke + ": " + relevance + " / " + invokeProbability + " / " + getFastPathMinProbability() + " / " + getScopeRelevanceWithinParent();
247            return relevance;
248        }
249    }
250
251    /**
252     * Computes the minimum probability along the most probable path within the scope. During
253     * iteration, the method returns immediately once a loop exit is discovered.
254     */
255    private double computeFastPathMinProbability(FixedNode scopeStart) {
256        ArrayList<FixedNode> pathBeginNodes = new ArrayList<>();
257        pathBeginNodes.add(scopeStart);
258        double minPathProbability = nodeProbabilities.applyAsDouble(scopeStart);
259        boolean isLoopScope = scopeStart instanceof LoopBeginNode;
260
261        do {
262            Node current = pathBeginNodes.remove(pathBeginNodes.size() - 1);
263            do {
264                if (isLoopScope && current instanceof LoopExitNode && ((LoopBeginNode) scopeStart).loopExits().contains((LoopExitNode) current)) {
265                    return minPathProbability;
266                } else if (current instanceof LoopBeginNode && current != scopeStart) {
267                    current = getMaxProbabilityLoopExit((LoopBeginNode) current, pathBeginNodes);
268                    minPathProbability = getMinPathProbability((FixedNode) current, minPathProbability);
269                } else if (current instanceof ControlSplitNode) {
270                    current = getMaxProbabilitySux((ControlSplitNode) current, pathBeginNodes);
271                    minPathProbability = getMinPathProbability((FixedNode) current, minPathProbability);
272                } else {
273                    assert current.successors().count() <= 1;
274                    current = current.successors().first();
275                }
276            } while (current != null);
277        } while (!pathBeginNodes.isEmpty());
278
279        return minPathProbability;
280    }
281
282    private double getMinPathProbability(FixedNode current, double minPathProbability) {
283        return current == null ? minPathProbability : Math.min(minPathProbability, nodeProbabilities.applyAsDouble(current));
284    }
285
286    /**
287     * Returns the most probable successor. If multiple successors share the maximum probability,
288     * one is returned and the others are enqueued in pathBeginNodes.
289     */
290    private static Node getMaxProbabilitySux(ControlSplitNode controlSplit, ArrayList<FixedNode> pathBeginNodes) {
291        Node maxSux = null;
292        double maxProbability = 0.0;
293        int pathBeginCount = pathBeginNodes.size();
294
295        for (Node sux : controlSplit.successors()) {
296            double probability = controlSplit.probability((AbstractBeginNode) sux);
297            if (probability > maxProbability) {
298                maxProbability = probability;
299                maxSux = sux;
300                truncate(pathBeginNodes, pathBeginCount);
301            } else if (probability == maxProbability) {
302                pathBeginNodes.add((FixedNode) sux);
303            }
304        }
305
306        return maxSux;
307    }
308
309    /**
310     * Returns the most probable loop exit. If multiple successors share the maximum probability,
311     * one is returned and the others are enqueued in pathBeginNodes.
312     */
313    private Node getMaxProbabilityLoopExit(LoopBeginNode loopBegin, ArrayList<FixedNode> pathBeginNodes) {
314        Node maxSux = null;
315        double maxProbability = 0.0;
316        int pathBeginCount = pathBeginNodes.size();
317
318        for (LoopExitNode sux : loopBegin.loopExits()) {
319            double probability = nodeProbabilities.applyAsDouble(sux);
320            if (probability > maxProbability) {
321                maxProbability = probability;
322                maxSux = sux;
323                truncate(pathBeginNodes, pathBeginCount);
324            } else if (probability == maxProbability) {
325                pathBeginNodes.add(sux);
326            }
327        }
328
329        return maxSux;
330    }
331
332    private static void truncate(ArrayList<FixedNode> pathBeginNodes, int pathBeginCount) {
333        for (int i = pathBeginNodes.size() - pathBeginCount; i > 0; i--) {
334            pathBeginNodes.remove(pathBeginNodes.size() - 1);
335        }
336    }
337}
338