1 /* 2 * Copyright (c) 2011, 2016, 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 24 25 package org.graalvm.compiler.phases.common.inlining.walker; 26 27 import static org.graalvm.compiler.core.common.GraalOptions.Intrinsify; 28 import static org.graalvm.compiler.core.common.GraalOptions.MaximumRecursiveInlining; 29 import static org.graalvm.compiler.core.common.GraalOptions.MegamorphicInliningMinMethodProbability; 30 31 import java.util.ArrayDeque; 32 import java.util.ArrayList; 33 import java.util.BitSet; 34 import java.util.Collection; 35 import java.util.Iterator; 36 import java.util.LinkedList; 37 import java.util.List; 38 39 import jdk.internal.vm.compiler.collections.EconomicSet; 40 import jdk.internal.vm.compiler.collections.Equivalence; 41 import org.graalvm.compiler.core.common.type.ObjectStamp; 42 import org.graalvm.compiler.debug.CounterKey; 43 import org.graalvm.compiler.debug.DebugContext; 44 import org.graalvm.compiler.debug.GraalError; 45 import org.graalvm.compiler.graph.Graph; 46 import org.graalvm.compiler.graph.Node; 47 import org.graalvm.compiler.nodes.CallTargetNode; 48 import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind; 49 import org.graalvm.compiler.nodes.Invoke; 50 import org.graalvm.compiler.nodes.NodeView; 51 import org.graalvm.compiler.nodes.ParameterNode; 52 import org.graalvm.compiler.nodes.StructuredGraph; 53 import org.graalvm.compiler.nodes.ValueNode; 54 import org.graalvm.compiler.nodes.java.AbstractNewObjectNode; 55 import org.graalvm.compiler.nodes.java.MethodCallTargetNode; 56 import org.graalvm.compiler.nodes.virtual.AllocatedObjectNode; 57 import org.graalvm.compiler.nodes.virtual.VirtualObjectNode; 58 import org.graalvm.compiler.options.OptionValues; 59 import org.graalvm.compiler.phases.OptimisticOptimizations; 60 import org.graalvm.compiler.phases.common.CanonicalizerPhase; 61 import org.graalvm.compiler.phases.common.inlining.InliningUtil; 62 import org.graalvm.compiler.phases.common.inlining.info.AssumptionInlineInfo; 63 import org.graalvm.compiler.phases.common.inlining.info.ExactInlineInfo; 64 import org.graalvm.compiler.phases.common.inlining.info.InlineInfo; 65 import org.graalvm.compiler.phases.common.inlining.info.MultiTypeGuardInlineInfo; 66 import org.graalvm.compiler.phases.common.inlining.info.TypeGuardInlineInfo; 67 import org.graalvm.compiler.phases.common.inlining.info.elem.Inlineable; 68 import org.graalvm.compiler.phases.common.inlining.info.elem.InlineableGraph; 69 import org.graalvm.compiler.phases.common.inlining.policy.InliningPolicy; 70 import org.graalvm.compiler.phases.tiers.HighTierContext; 71 import org.graalvm.compiler.phases.util.Providers; 72 73 import jdk.vm.ci.code.BailoutException; 74 import jdk.vm.ci.meta.Assumptions.AssumptionResult; 75 import jdk.vm.ci.meta.JavaTypeProfile; 76 import jdk.vm.ci.meta.ResolvedJavaMethod; 77 import jdk.vm.ci.meta.ResolvedJavaType; 78 79 /** 80 * <p> 81 * The space of inlining decisions is explored depth-first with the help of a stack realized by 82 * {@link InliningData}. At any point in time, the topmost element of that stack consists of: 83 * <ul> 84 * <li>the callsite under consideration is tracked as a {@link MethodInvocation}.</li> 85 * <li>one or more {@link CallsiteHolder}s, all of them associated to the callsite above. Why more 86 * than one? Depending on the type-profile for the receiver more than one concrete method may be 87 * feasible target.</li> 88 * </ul> 89 * </p> 90 * 91 * <p> 92 * The bottom element in the stack consists of: 93 * <ul> 94 * <li>a single {@link MethodInvocation} (the 95 * {@link org.graalvm.compiler.phases.common.inlining.walker.MethodInvocation#isRoot root} one, ie 96 * the unknown caller of the root graph)</li> 97 * <li>a single {@link CallsiteHolder} (the root one, for the method on which inlining was called) 98 * </li> 99 * </ul> 100 * </p> 101 * 102 * @see #moveForward() 103 */ 104 public class InliningData { 105 106 // Counters 107 private static final CounterKey counterInliningPerformed = DebugContext.counter("InliningPerformed"); 108 private static final CounterKey counterInliningRuns = DebugContext.counter("InliningRuns"); 109 private static final CounterKey counterInliningConsidered = DebugContext.counter("InliningConsidered"); 110 111 /** 112 * Call hierarchy from outer most call (i.e., compilation unit) to inner most callee. 113 */ 114 private final ArrayDeque<CallsiteHolder> graphQueue = new ArrayDeque<>(); 115 private final ArrayDeque<MethodInvocation> invocationQueue = new ArrayDeque<>(); 116 117 private final HighTierContext context; 118 private final int maxMethodPerInlining; 119 private final CanonicalizerPhase canonicalizer; 120 private final InliningPolicy inliningPolicy; 121 private final StructuredGraph rootGraph; 122 private final DebugContext debug; 123 124 private int maxGraphs; 125 InliningData(StructuredGraph rootGraph, HighTierContext context, int maxMethodPerInlining, CanonicalizerPhase canonicalizer, InliningPolicy inliningPolicy, LinkedList<Invoke> rootInvokes)126 public InliningData(StructuredGraph rootGraph, HighTierContext context, int maxMethodPerInlining, CanonicalizerPhase canonicalizer, InliningPolicy inliningPolicy, LinkedList<Invoke> rootInvokes) { 127 assert rootGraph != null; 128 this.context = context; 129 this.maxMethodPerInlining = maxMethodPerInlining; 130 this.canonicalizer = canonicalizer; 131 this.inliningPolicy = inliningPolicy; 132 this.maxGraphs = 1; 133 this.rootGraph = rootGraph; 134 this.debug = rootGraph.getDebug(); 135 136 invocationQueue.push(new MethodInvocation(null, 1.0, 1.0, null)); 137 graphQueue.push(new CallsiteHolderExplorable(rootGraph, 1.0, 1.0, null, rootInvokes)); 138 } 139 isFreshInstantiation(ValueNode arg)140 public static boolean isFreshInstantiation(ValueNode arg) { 141 return (arg instanceof AbstractNewObjectNode) || (arg instanceof AllocatedObjectNode) || (arg instanceof VirtualObjectNode); 142 } 143 checkTargetConditionsHelper(ResolvedJavaMethod method, int invokeBci)144 private String checkTargetConditionsHelper(ResolvedJavaMethod method, int invokeBci) { 145 OptionValues options = rootGraph.getOptions(); 146 if (method == null) { 147 return "the method is not resolved"; 148 } else if (method.isNative() && (!Intrinsify.getValue(options) || !InliningUtil.canIntrinsify(context.getReplacements(), method, invokeBci))) { 149 return "it is a non-intrinsic native method"; 150 } else if (method.isAbstract()) { 151 return "it is an abstract method"; 152 } else if (!method.getDeclaringClass().isInitialized()) { 153 return "the method's class is not initialized"; 154 } else if (!method.canBeInlined()) { 155 return "it is marked non-inlinable"; 156 } else if (countRecursiveInlining(method) > MaximumRecursiveInlining.getValue(options)) { 157 return "it exceeds the maximum recursive inlining depth"; 158 } else { 159 if (new OptimisticOptimizations(rootGraph.getProfilingInfo(method), options).lessOptimisticThan(context.getOptimisticOptimizations())) { 160 return "the callee uses less optimistic optimizations than caller"; 161 } else { 162 return null; 163 } 164 } 165 } 166 checkTargetConditions(Invoke invoke, ResolvedJavaMethod method)167 private boolean checkTargetConditions(Invoke invoke, ResolvedJavaMethod method) { 168 final String failureMessage = checkTargetConditionsHelper(method, invoke.bci()); 169 if (failureMessage == null) { 170 return true; 171 } else { 172 InliningUtil.traceNotInlinedMethod(invoke, inliningDepth(), method, failureMessage); 173 invoke.asNode().graph().getInliningLog().addDecision(invoke, false, "InliningPhase", null, null, failureMessage); 174 return false; 175 } 176 } 177 178 /** 179 * Determines if inlining is possible at the given invoke node. 180 * 181 * @param invoke the invoke that should be inlined 182 * @return an instance of InlineInfo, or null if no inlining is possible at the given invoke 183 */ getInlineInfo(Invoke invoke)184 private InlineInfo getInlineInfo(Invoke invoke) { 185 final String failureMessage = InliningUtil.checkInvokeConditions(invoke); 186 if (failureMessage != null) { 187 InliningUtil.logNotInlinedMethod(invoke, failureMessage); 188 return null; 189 } 190 MethodCallTargetNode callTarget = (MethodCallTargetNode) invoke.callTarget(); 191 ResolvedJavaMethod targetMethod = callTarget.targetMethod(); 192 193 InvokeKind invokeKind = callTarget.invokeKind(); 194 if (invokeKind == CallTargetNode.InvokeKind.Special || invokeKind == CallTargetNode.InvokeKind.Static || targetMethod.canBeStaticallyBound()) { 195 return getExactInlineInfo(invoke, targetMethod); 196 } 197 198 assert invokeKind.isIndirect(); 199 200 ResolvedJavaType holder = targetMethod.getDeclaringClass(); 201 if (!(callTarget.receiver().stamp(NodeView.DEFAULT) instanceof ObjectStamp)) { 202 return null; 203 } 204 ObjectStamp receiverStamp = (ObjectStamp) callTarget.receiver().stamp(NodeView.DEFAULT); 205 if (receiverStamp.alwaysNull()) { 206 // Don't inline if receiver is known to be null 207 return null; 208 } 209 ResolvedJavaType contextType = invoke.getContextType(); 210 if (receiverStamp.type() != null) { 211 // the invoke target might be more specific than the holder (happens after inlining: 212 // parameters lose their declared type...) 213 ResolvedJavaType receiverType = receiverStamp.type(); 214 if (receiverType != null && holder.isAssignableFrom(receiverType)) { 215 holder = receiverType; 216 if (receiverStamp.isExactType()) { 217 assert targetMethod.getDeclaringClass().isAssignableFrom(holder) : holder + " subtype of " + targetMethod.getDeclaringClass() + " for " + targetMethod; 218 ResolvedJavaMethod resolvedMethod = holder.resolveConcreteMethod(targetMethod, contextType); 219 if (resolvedMethod != null) { 220 return getExactInlineInfo(invoke, resolvedMethod); 221 } 222 } 223 } 224 } 225 226 if (holder.isArray()) { 227 // arrays can be treated as Objects 228 ResolvedJavaMethod resolvedMethod = holder.resolveConcreteMethod(targetMethod, contextType); 229 if (resolvedMethod != null) { 230 return getExactInlineInfo(invoke, resolvedMethod); 231 } 232 } 233 234 if (invokeKind != InvokeKind.Interface) { 235 AssumptionResult<ResolvedJavaType> leafConcreteSubtype = holder.findLeafConcreteSubtype(); 236 if (leafConcreteSubtype != null) { 237 ResolvedJavaMethod resolvedMethod = leafConcreteSubtype.getResult().resolveConcreteMethod(targetMethod, contextType); 238 if (resolvedMethod != null) { 239 if (leafConcreteSubtype.canRecordTo(callTarget.graph().getAssumptions())) { 240 return getAssumptionInlineInfo(invoke, resolvedMethod, leafConcreteSubtype); 241 } else { 242 return getTypeCheckedAssumptionInfo(invoke, resolvedMethod, leafConcreteSubtype.getResult()); 243 } 244 } 245 } 246 247 AssumptionResult<ResolvedJavaMethod> concrete = holder.findUniqueConcreteMethod(targetMethod); 248 if (concrete != null && concrete.canRecordTo(callTarget.graph().getAssumptions())) { 249 return getAssumptionInlineInfo(invoke, concrete.getResult(), concrete); 250 } 251 } 252 253 // type check based inlining 254 return getTypeCheckedInlineInfo(invoke, targetMethod); 255 } 256 getTypeCheckedAssumptionInfo(Invoke invoke, ResolvedJavaMethod method, ResolvedJavaType type)257 private InlineInfo getTypeCheckedAssumptionInfo(Invoke invoke, ResolvedJavaMethod method, ResolvedJavaType type) { 258 if (!checkTargetConditions(invoke, method)) { 259 return null; 260 } 261 return new TypeGuardInlineInfo(invoke, method, type); 262 } 263 getTypeCheckedInlineInfo(Invoke invoke, ResolvedJavaMethod targetMethod)264 private InlineInfo getTypeCheckedInlineInfo(Invoke invoke, ResolvedJavaMethod targetMethod) { 265 JavaTypeProfile typeProfile = ((MethodCallTargetNode) invoke.callTarget()).getProfile(); 266 if (typeProfile == null) { 267 InliningUtil.traceNotInlinedMethod(invoke, inliningDepth(), targetMethod, "no type profile exists"); 268 invoke.asNode().graph().getInliningLog().addDecision(invoke, false, "InliningPhase", null, null, "no type profile exists"); 269 return null; 270 } 271 272 JavaTypeProfile.ProfiledType[] ptypes = typeProfile.getTypes(); 273 if (ptypes == null || ptypes.length <= 0) { 274 InliningUtil.traceNotInlinedMethod(invoke, inliningDepth(), targetMethod, "no types in profile"); 275 invoke.asNode().graph().getInliningLog().addDecision(invoke, false, "InliningPhase", null, null, "no types in profile"); 276 return null; 277 } 278 ResolvedJavaType contextType = invoke.getContextType(); 279 double notRecordedTypeProbability = typeProfile.getNotRecordedProbability(); 280 final OptimisticOptimizations optimisticOpts = context.getOptimisticOptimizations(); 281 OptionValues options = invoke.asNode().getOptions(); 282 if (ptypes.length == 1 && notRecordedTypeProbability == 0) { 283 if (!optimisticOpts.inlineMonomorphicCalls(options)) { 284 InliningUtil.traceNotInlinedMethod(invoke, inliningDepth(), targetMethod, "inlining monomorphic calls is disabled"); 285 invoke.asNode().graph().getInliningLog().addDecision(invoke, false, "InliningPhase", null, null, "inlining monomorphic calls is disabled"); 286 return null; 287 } 288 289 ResolvedJavaType type = ptypes[0].getType(); 290 assert type.isArray() || type.isConcrete(); 291 ResolvedJavaMethod concrete = type.resolveConcreteMethod(targetMethod, contextType); 292 if (!checkTargetConditions(invoke, concrete)) { 293 return null; 294 } 295 return new TypeGuardInlineInfo(invoke, concrete, type); 296 } else { 297 invoke.setPolymorphic(true); 298 299 if (!optimisticOpts.inlinePolymorphicCalls(options) && notRecordedTypeProbability == 0) { 300 InliningUtil.traceNotInlinedMethod(invoke, inliningDepth(), targetMethod, "inlining polymorphic calls is disabled (%d types)", ptypes.length); 301 invoke.asNode().graph().getInliningLog().addDecision(invoke, false, "InliningPhase", null, null, "inlining polymorphic calls is disabled (%d types)", ptypes.length); 302 return null; 303 } 304 if (!optimisticOpts.inlineMegamorphicCalls(options) && notRecordedTypeProbability > 0) { 305 // due to filtering impossible types, notRecordedTypeProbability can be > 0 although 306 // the number of types is lower than what can be recorded in a type profile 307 InliningUtil.traceNotInlinedMethod(invoke, inliningDepth(), targetMethod, "inlining megamorphic calls is disabled (%d types, %f %% not recorded types)", ptypes.length, 308 notRecordedTypeProbability * 100); 309 invoke.asNode().graph().getInliningLog().addDecision(invoke, false, "InliningPhase", null, null, 310 "inlining megamorphic calls is disabled (%d types, %f %% not recorded types)", ptypes.length, notRecordedTypeProbability); 311 return null; 312 } 313 314 // Find unique methods and their probabilities. 315 ArrayList<ResolvedJavaMethod> concreteMethods = new ArrayList<>(); 316 ArrayList<Double> concreteMethodsProbabilities = new ArrayList<>(); 317 for (int i = 0; i < ptypes.length; i++) { 318 ResolvedJavaMethod concrete = ptypes[i].getType().resolveConcreteMethod(targetMethod, contextType); 319 if (concrete == null) { 320 InliningUtil.traceNotInlinedMethod(invoke, inliningDepth(), targetMethod, "could not resolve method"); 321 invoke.asNode().graph().getInliningLog().addDecision(invoke, false, "InliningPhase", null, null, "could not resolve method"); 322 return null; 323 } 324 int index = concreteMethods.indexOf(concrete); 325 double curProbability = ptypes[i].getProbability(); 326 if (index < 0) { 327 index = concreteMethods.size(); 328 concreteMethods.add(concrete); 329 concreteMethodsProbabilities.add(curProbability); 330 } else { 331 concreteMethodsProbabilities.set(index, concreteMethodsProbabilities.get(index) + curProbability); 332 } 333 } 334 335 // Clear methods that fall below the threshold. 336 if (notRecordedTypeProbability > 0) { 337 ArrayList<ResolvedJavaMethod> newConcreteMethods = new ArrayList<>(); 338 ArrayList<Double> newConcreteMethodsProbabilities = new ArrayList<>(); 339 for (int i = 0; i < concreteMethods.size(); ++i) { 340 if (concreteMethodsProbabilities.get(i) >= MegamorphicInliningMinMethodProbability.getValue(options)) { 341 newConcreteMethods.add(concreteMethods.get(i)); 342 newConcreteMethodsProbabilities.add(concreteMethodsProbabilities.get(i)); 343 } 344 } 345 346 if (newConcreteMethods.isEmpty()) { 347 // No method left that is worth inlining. 348 InliningUtil.traceNotInlinedMethod(invoke, inliningDepth(), targetMethod, "no methods remaining after filtering less frequent methods (%d methods previously)", 349 concreteMethods.size()); 350 invoke.asNode().graph().getInliningLog().addDecision(invoke, false, "InliningPhase", null, null, 351 "no methods remaining after filtering less frequent methods (%d methods previously)", concreteMethods.size()); 352 return null; 353 } 354 355 concreteMethods = newConcreteMethods; 356 concreteMethodsProbabilities = newConcreteMethodsProbabilities; 357 } 358 359 if (concreteMethods.size() > maxMethodPerInlining) { 360 InliningUtil.traceNotInlinedMethod(invoke, inliningDepth(), targetMethod, "polymorphic call with more than %d target methods", maxMethodPerInlining); 361 invoke.asNode().graph().getInliningLog().addDecision(invoke, false, "InliningPhase", null, null, "polymorphic call with more than %d target methods", maxMethodPerInlining); 362 return null; 363 } 364 365 // Clean out types whose methods are no longer available. 366 ArrayList<JavaTypeProfile.ProfiledType> usedTypes = new ArrayList<>(); 367 ArrayList<Integer> typesToConcretes = new ArrayList<>(); 368 for (JavaTypeProfile.ProfiledType type : ptypes) { 369 ResolvedJavaMethod concrete = type.getType().resolveConcreteMethod(targetMethod, contextType); 370 int index = concreteMethods.indexOf(concrete); 371 if (index == -1) { 372 notRecordedTypeProbability += type.getProbability(); 373 } else { 374 assert type.getType().isArray() || !type.getType().isAbstract() : type + " " + concrete; 375 usedTypes.add(type); 376 typesToConcretes.add(index); 377 } 378 } 379 380 if (usedTypes.isEmpty()) { 381 // No type left that is worth checking for. 382 InliningUtil.traceNotInlinedMethod(invoke, inliningDepth(), targetMethod, "no types remaining after filtering less frequent types (%d types previously)", ptypes.length); 383 invoke.asNode().graph().getInliningLog().addDecision(invoke, false, "InliningPhase", null, null, "no types remaining after filtering less frequent types (%d types previously)", 384 ptypes.length); 385 return null; 386 } 387 388 for (ResolvedJavaMethod concrete : concreteMethods) { 389 if (!checkTargetConditions(invoke, concrete)) { 390 InliningUtil.traceNotInlinedMethod(invoke, inliningDepth(), targetMethod, "it is a polymorphic method call and at least one invoked method cannot be inlined"); 391 invoke.asNode().graph().getInliningLog().addDecision(invoke, false, "InliningPhase", null, null, 392 "it is a polymorphic method call and at least one invoked method cannot be inlined"); 393 return null; 394 } 395 } 396 return new MultiTypeGuardInlineInfo(invoke, concreteMethods, usedTypes, typesToConcretes, notRecordedTypeProbability); 397 } 398 } 399 getAssumptionInlineInfo(Invoke invoke, ResolvedJavaMethod concrete, AssumptionResult<?> takenAssumption)400 private InlineInfo getAssumptionInlineInfo(Invoke invoke, ResolvedJavaMethod concrete, AssumptionResult<?> takenAssumption) { 401 assert concrete.isConcrete(); 402 if (checkTargetConditions(invoke, concrete)) { 403 return new AssumptionInlineInfo(invoke, concrete, takenAssumption); 404 } 405 return null; 406 } 407 getExactInlineInfo(Invoke invoke, ResolvedJavaMethod targetMethod)408 private InlineInfo getExactInlineInfo(Invoke invoke, ResolvedJavaMethod targetMethod) { 409 assert targetMethod.isConcrete(); 410 if (checkTargetConditions(invoke, targetMethod)) { 411 return new ExactInlineInfo(invoke, targetMethod); 412 } 413 return null; 414 } 415 416 @SuppressWarnings("try") doInline(CallsiteHolderExplorable callerCallsiteHolder, MethodInvocation calleeInvocation, String reason)417 private void doInline(CallsiteHolderExplorable callerCallsiteHolder, MethodInvocation calleeInvocation, String reason) { 418 StructuredGraph callerGraph = callerCallsiteHolder.graph(); 419 InlineInfo calleeInfo = calleeInvocation.callee(); 420 try { 421 try (DebugContext.Scope scope = debug.scope("doInline", callerGraph)) { 422 EconomicSet<Node> canonicalizedNodes = EconomicSet.create(Equivalence.IDENTITY); 423 canonicalizedNodes.addAll(calleeInfo.invoke().asNode().usages()); 424 EconomicSet<Node> parameterUsages = calleeInfo.inline(new Providers(context), reason); 425 canonicalizedNodes.addAll(parameterUsages); 426 counterInliningRuns.increment(debug); 427 debug.dump(DebugContext.DETAILED_LEVEL, callerGraph, "after %s", calleeInfo); 428 429 Graph.Mark markBeforeCanonicalization = callerGraph.getMark(); 430 431 canonicalizer.applyIncremental(callerGraph, context, canonicalizedNodes); 432 433 // process invokes that are possibly created during canonicalization 434 for (Node newNode : callerGraph.getNewNodes(markBeforeCanonicalization)) { 435 if (newNode instanceof Invoke) { 436 callerCallsiteHolder.pushInvoke((Invoke) newNode); 437 } 438 } 439 440 callerCallsiteHolder.computeProbabilities(); 441 442 counterInliningPerformed.increment(debug); 443 } 444 } catch (BailoutException bailout) { 445 throw bailout; 446 } catch (AssertionError | RuntimeException e) { 447 throw new GraalError(e).addContext(calleeInfo.toString()); 448 } catch (GraalError e) { 449 throw e.addContext(calleeInfo.toString()); 450 } catch (Throwable e) { 451 throw debug.handle(e); 452 } 453 } 454 455 /** 456 * 457 * This method attempts: 458 * <ol> 459 * <li>to inline at the callsite given by <code>calleeInvocation</code>, where that callsite 460 * belongs to the {@link CallsiteHolderExplorable} at the top of the {@link #graphQueue} 461 * maintained in this class.</li> 462 * <li>otherwise, to devirtualize the callsite in question.</li> 463 * </ol> 464 * 465 * @return true iff inlining was actually performed 466 */ tryToInline(MethodInvocation calleeInvocation, int inliningDepth)467 private boolean tryToInline(MethodInvocation calleeInvocation, int inliningDepth) { 468 CallsiteHolderExplorable callerCallsiteHolder = (CallsiteHolderExplorable) currentGraph(); 469 InlineInfo calleeInfo = calleeInvocation.callee(); 470 assert callerCallsiteHolder.containsInvoke(calleeInfo.invoke()); 471 counterInliningConsidered.increment(debug); 472 473 InliningPolicy.Decision decision = inliningPolicy.isWorthInlining(context.getReplacements(), calleeInvocation, inliningDepth, true); 474 if (decision.shouldInline()) { 475 doInline(callerCallsiteHolder, calleeInvocation, decision.getReason()); 476 return true; 477 } 478 479 if (context.getOptimisticOptimizations().devirtualizeInvokes(calleeInfo.graph().getOptions())) { 480 calleeInfo.tryToDevirtualizeInvoke(new Providers(context)); 481 } 482 483 return false; 484 } 485 486 /** 487 * This method picks one of the callsites belonging to the current 488 * {@link CallsiteHolderExplorable}. Provided the callsite qualifies to be analyzed for 489 * inlining, this method prepares a new stack top in {@link InliningData} for such callsite, 490 * which comprises: 491 * <ul> 492 * <li>preparing a summary of feasible targets, ie preparing an {@link InlineInfo}</li> 493 * <li>based on it, preparing the stack top proper which consists of:</li> 494 * <ul> 495 * <li>one {@link MethodInvocation}</li> 496 * <li>a {@link CallsiteHolder} for each feasible target</li> 497 * </ul> 498 * </ul> 499 * 500 * <p> 501 * The thus prepared "stack top" is needed by {@link #moveForward()} to explore the space of 502 * inlining decisions (each decision one of: backtracking, delving, inlining). 503 * </p> 504 * 505 * <p> 506 * The {@link InlineInfo} used to get things rolling is kept around in the 507 * {@link MethodInvocation}, it will be needed in case of inlining, see 508 * {@link InlineInfo#inline(Providers, String)} 509 * </p> 510 */ processNextInvoke()511 private void processNextInvoke() { 512 CallsiteHolderExplorable callsiteHolder = (CallsiteHolderExplorable) currentGraph(); 513 Invoke invoke = callsiteHolder.popInvoke(); 514 InlineInfo info = getInlineInfo(invoke); 515 516 if (info != null) { 517 info.populateInlinableElements(context, currentGraph().graph(), canonicalizer, rootGraph.getOptions()); 518 double invokeProbability = callsiteHolder.invokeProbability(invoke); 519 double invokeRelevance = callsiteHolder.invokeRelevance(invoke); 520 MethodInvocation methodInvocation = new MethodInvocation(info, invokeProbability, invokeRelevance, freshlyInstantiatedArguments(invoke, callsiteHolder.getFixedParams())); 521 pushInvocationAndGraphs(methodInvocation); 522 } 523 } 524 525 /** 526 * Gets the freshly instantiated arguments. 527 * <p> 528 * A freshly instantiated argument is either: 529 * <uL> 530 * <li>an {@link InliningData#isFreshInstantiation(org.graalvm.compiler.nodes.ValueNode)}</li> 531 * <li>a fixed-param, ie a {@link ParameterNode} receiving a freshly instantiated argument</li> 532 * </uL> 533 * </p> 534 * 535 * @return the positions of freshly instantiated arguments in the argument list of the 536 * <code>invoke</code>, or null if no such positions exist. 537 */ freshlyInstantiatedArguments(Invoke invoke, EconomicSet<ParameterNode> fixedParams)538 public static BitSet freshlyInstantiatedArguments(Invoke invoke, EconomicSet<ParameterNode> fixedParams) { 539 assert fixedParams != null; 540 assert paramsAndInvokeAreInSameGraph(invoke, fixedParams); 541 BitSet result = null; 542 int argIdx = 0; 543 for (ValueNode arg : invoke.callTarget().arguments()) { 544 assert arg != null; 545 if (isFreshInstantiation(arg) || (arg instanceof ParameterNode && fixedParams.contains((ParameterNode) arg))) { 546 if (result == null) { 547 result = new BitSet(); 548 } 549 result.set(argIdx); 550 } 551 argIdx++; 552 } 553 return result; 554 } 555 paramsAndInvokeAreInSameGraph(Invoke invoke, EconomicSet<ParameterNode> fixedParams)556 private static boolean paramsAndInvokeAreInSameGraph(Invoke invoke, EconomicSet<ParameterNode> fixedParams) { 557 if (fixedParams.isEmpty()) { 558 return true; 559 } 560 for (ParameterNode p : fixedParams) { 561 if (p.graph() != invoke.asNode().graph()) { 562 return false; 563 } 564 } 565 return true; 566 } 567 graphCount()568 public int graphCount() { 569 return graphQueue.size(); 570 } 571 hasUnprocessedGraphs()572 public boolean hasUnprocessedGraphs() { 573 return !graphQueue.isEmpty(); 574 } 575 currentGraph()576 private CallsiteHolder currentGraph() { 577 return graphQueue.peek(); 578 } 579 popGraph()580 private void popGraph() { 581 graphQueue.pop(); 582 assert graphQueue.size() <= maxGraphs; 583 } 584 popGraphs(int count)585 private void popGraphs(int count) { 586 assert count >= 0; 587 for (int i = 0; i < count; i++) { 588 graphQueue.pop(); 589 } 590 } 591 592 private static final Object[] NO_CONTEXT = {}; 593 594 /** 595 * Gets the call hierarchy of this inlining from outer most call to inner most callee. 596 */ inliningContext()597 private Object[] inliningContext() { 598 if (!debug.isDumpEnabled(DebugContext.INFO_LEVEL)) { 599 return NO_CONTEXT; 600 } 601 Object[] result = new Object[graphQueue.size()]; 602 int i = 0; 603 for (CallsiteHolder g : graphQueue) { 604 result[i++] = g.method(); 605 } 606 return result; 607 } 608 currentInvocation()609 private MethodInvocation currentInvocation() { 610 return invocationQueue.peekFirst(); 611 } 612 pushInvocationAndGraphs(MethodInvocation methodInvocation)613 private void pushInvocationAndGraphs(MethodInvocation methodInvocation) { 614 invocationQueue.addFirst(methodInvocation); 615 InlineInfo info = methodInvocation.callee(); 616 maxGraphs += info.numberOfMethods(); 617 assert graphQueue.size() <= maxGraphs; 618 for (int i = 0; i < info.numberOfMethods(); i++) { 619 CallsiteHolder ch = methodInvocation.buildCallsiteHolderForElement(i); 620 assert !contains(ch.graph()); 621 graphQueue.push(ch); 622 assert graphQueue.size() <= maxGraphs; 623 } 624 } 625 popInvocation()626 private void popInvocation() { 627 maxGraphs -= invocationQueue.peekFirst().callee().numberOfMethods(); 628 assert graphQueue.size() <= maxGraphs; 629 invocationQueue.removeFirst(); 630 } 631 countRecursiveInlining(ResolvedJavaMethod method)632 public int countRecursiveInlining(ResolvedJavaMethod method) { 633 int count = 0; 634 for (CallsiteHolder callsiteHolder : graphQueue) { 635 if (method.equals(callsiteHolder.method())) { 636 count++; 637 } 638 } 639 return count; 640 } 641 inliningDepth()642 public int inliningDepth() { 643 assert invocationQueue.size() > 0; 644 return invocationQueue.size() - 1; 645 } 646 647 @Override toString()648 public String toString() { 649 StringBuilder result = new StringBuilder("Invocations: "); 650 651 for (MethodInvocation invocation : invocationQueue) { 652 if (invocation.callee() != null) { 653 result.append(invocation.callee().numberOfMethods()); 654 result.append("x "); 655 result.append(invocation.callee().invoke()); 656 result.append("; "); 657 } 658 } 659 660 result.append("\nGraphs: "); 661 for (CallsiteHolder graph : graphQueue) { 662 result.append(graph.graph()); 663 result.append("; "); 664 } 665 666 return result.toString(); 667 } 668 669 /** 670 * Gets a stack trace representing the current inlining stack represented by this object. 671 */ getInvocationStackTrace()672 public Collection<StackTraceElement> getInvocationStackTrace() { 673 List<StackTraceElement> result = new ArrayList<>(); 674 for (CallsiteHolder graph : graphQueue) { 675 result.add(graph.method().asStackTraceElement(0)); 676 } 677 678 return result; 679 } 680 contains(StructuredGraph graph)681 private boolean contains(StructuredGraph graph) { 682 assert graph != null; 683 for (CallsiteHolder info : graphQueue) { 684 if (info.graph() == graph) { 685 return true; 686 } 687 } 688 return false; 689 } 690 691 /** 692 * <p> 693 * The stack realized by {@link InliningData} grows and shrinks as choices are made among the 694 * alternatives below: 695 * <ol> 696 * <li>not worth inlining: pop stack top, which comprises: 697 * <ul> 698 * <li>pop any remaining graphs not yet delved into</li> 699 * <li>pop the current invocation</li> 700 * </ul> 701 * </li> 702 * <li>{@link #processNextInvoke() delve} into one of the callsites hosted in the current graph, 703 * such callsite is explored next by {@link #moveForward()}</li> 704 * <li>{@link #tryToInline(MethodInvocation, int) try to inline}: move past the current graph 705 * (remove it from the topmost element). 706 * <ul> 707 * <li>If that was the last one then {@link #tryToInline(MethodInvocation, int) try to inline} 708 * the callsite under consideration (ie, the "current invocation").</li> 709 * <li>Whether inlining occurs or not, that callsite is removed from the top of 710 * {@link InliningData} .</li> 711 * </ul> 712 * </li> 713 * </ol> 714 * </p> 715 * 716 * <p> 717 * Some facts about the alternatives above: 718 * <ul> 719 * <li>the first step amounts to backtracking, the 2nd one to depth-search, and the 3rd one also 720 * involves backtracking (however possibly after inlining).</li> 721 * <li>the choice of abandon-and-backtrack or delve-into depends on 722 * {@link InliningPolicy#isWorthInlining} and {@link InliningPolicy#continueInlining}.</li> 723 * <li>the 3rd choice is picked whenever none of the previous choices are made</li> 724 * </ul> 725 * </p> 726 * 727 * @return true iff inlining was actually performed 728 */ 729 @SuppressWarnings("try") moveForward()730 public boolean moveForward() { 731 732 final MethodInvocation currentInvocation = currentInvocation(); 733 734 final boolean backtrack = (!currentInvocation.isRoot() && !inliningPolicy.isWorthInlining(context.getReplacements(), currentInvocation, inliningDepth(), false).shouldInline()); 735 if (backtrack) { 736 int remainingGraphs = currentInvocation.totalGraphs() - currentInvocation.processedGraphs(); 737 assert remainingGraphs > 0; 738 popGraphs(remainingGraphs); 739 popInvocation(); 740 return false; 741 } 742 743 final boolean delve = currentGraph().hasRemainingInvokes() && inliningPolicy.continueInlining(currentGraph().graph()); 744 if (delve) { 745 processNextInvoke(); 746 return false; 747 } 748 749 popGraph(); 750 if (currentInvocation.isRoot()) { 751 return false; 752 } 753 754 // try to inline 755 assert currentInvocation.callee().invoke().asNode().isAlive(); 756 currentInvocation.incrementProcessedGraphs(); 757 if (currentInvocation.processedGraphs() == currentInvocation.totalGraphs()) { 758 /* 759 * "all of currentInvocation's graphs processed" amounts to 760 * "all concrete methods that come into question already had the callees they contain analyzed for inlining" 761 */ 762 popInvocation(); 763 try (DebugContext.Scope s = debug.scope("Inlining", inliningContext())) { 764 if (tryToInline(currentInvocation, inliningDepth() + 1)) { 765 // Report real progress only if we inline into the root graph 766 return currentGraph().graph() == rootGraph; 767 } 768 return false; 769 } catch (Throwable e) { 770 throw debug.handle(e); 771 } 772 } 773 774 return false; 775 } 776 777 /** 778 * Checks an invariant that {@link #moveForward()} must maintain: "the top invocation records 779 * how many concrete target methods (for it) remain on the {@link #graphQueue}; those targets 780 * 'belong' to the current invocation in question. 781 */ topGraphsForTopInvocation()782 private boolean topGraphsForTopInvocation() { 783 if (invocationQueue.isEmpty()) { 784 assert graphQueue.isEmpty(); 785 return true; 786 } 787 if (currentInvocation().isRoot()) { 788 if (!graphQueue.isEmpty()) { 789 assert graphQueue.size() == 1; 790 } 791 return true; 792 } 793 final int remainingGraphs = currentInvocation().totalGraphs() - currentInvocation().processedGraphs(); 794 final Iterator<CallsiteHolder> iter = graphQueue.iterator(); 795 for (int i = (remainingGraphs - 1); i >= 0; i--) { 796 if (!iter.hasNext()) { 797 assert false; 798 return false; 799 } 800 CallsiteHolder queuedTargetCH = iter.next(); 801 Inlineable targetIE = currentInvocation().callee().inlineableElementAt(i); 802 InlineableGraph targetIG = (InlineableGraph) targetIE; 803 assert queuedTargetCH.method().equals(targetIG.getGraph().method()); 804 } 805 return true; 806 } 807 808 /** 809 * This method checks invariants for this class. Named after shorthand for "internal 810 * representation is ok". 811 */ repOK()812 public boolean repOK() { 813 assert topGraphsForTopInvocation(); 814 return true; 815 } 816 } 817