1 /*
   2  * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.phases.common.inlining.walker;
  24 
  25 import java.util.ArrayList;
  26 import java.util.Map;
  27 import java.util.function.ToDoubleFunction;
  28 
  29 import org.graalvm.compiler.core.common.SuppressFBWarnings;
  30 import org.graalvm.compiler.graph.Node;
  31 import org.graalvm.compiler.graph.NodeWorkList;
  32 import org.graalvm.compiler.nodes.AbstractBeginNode;
  33 import org.graalvm.compiler.nodes.AbstractMergeNode;
  34 import org.graalvm.compiler.nodes.ControlSinkNode;
  35 import org.graalvm.compiler.nodes.ControlSplitNode;
  36 import org.graalvm.compiler.nodes.EndNode;
  37 import org.graalvm.compiler.nodes.FixedNode;
  38 import org.graalvm.compiler.nodes.FixedWithNextNode;
  39 import org.graalvm.compiler.nodes.Invoke;
  40 import org.graalvm.compiler.nodes.LoopBeginNode;
  41 import org.graalvm.compiler.nodes.LoopEndNode;
  42 import org.graalvm.compiler.nodes.LoopExitNode;
  43 import org.graalvm.compiler.nodes.MergeNode;
  44 import org.graalvm.compiler.nodes.StartNode;
  45 import org.graalvm.compiler.nodes.StructuredGraph;
  46 import org.graalvm.compiler.phases.common.inlining.InliningUtil;
  47 
  48 public class ComputeInliningRelevance {
  49 
  50     private static final double EPSILON = 1d / Integer.MAX_VALUE;
  51     private static final double UNINITIALIZED = -1D;
  52 
  53     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 }