1 /* 2 * Copyright (c) 2013, 2015, 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.info; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 28 import org.graalvm.compiler.core.common.type.StampFactory; 29 import org.graalvm.compiler.debug.Debug; 30 import org.graalvm.compiler.graph.Node; 31 import org.graalvm.compiler.nodes.AbstractBeginNode; 32 import org.graalvm.compiler.nodes.AbstractMergeNode; 33 import org.graalvm.compiler.nodes.BeginNode; 34 import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind; 35 import org.graalvm.compiler.nodes.DeoptimizeNode; 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.FrameState; 40 import org.graalvm.compiler.nodes.Invoke; 41 import org.graalvm.compiler.nodes.InvokeWithExceptionNode; 42 import org.graalvm.compiler.nodes.MergeNode; 43 import org.graalvm.compiler.nodes.PhiNode; 44 import org.graalvm.compiler.nodes.PiNode; 45 import org.graalvm.compiler.nodes.StructuredGraph; 46 import org.graalvm.compiler.nodes.ValueNode; 47 import org.graalvm.compiler.nodes.ValuePhiNode; 48 import org.graalvm.compiler.nodes.extended.LoadHubNode; 49 import org.graalvm.compiler.nodes.java.ExceptionObjectNode; 50 import org.graalvm.compiler.nodes.java.MethodCallTargetNode; 51 import org.graalvm.compiler.nodes.java.TypeSwitchNode; 52 import org.graalvm.compiler.nodes.spi.StampProvider; 53 import org.graalvm.compiler.nodes.util.GraphUtil; 54 import org.graalvm.compiler.phases.common.inlining.InliningUtil; 55 import org.graalvm.compiler.phases.common.inlining.info.elem.Inlineable; 56 import org.graalvm.compiler.phases.util.Providers; 57 import org.graalvm.util.EconomicSet; 58 import org.graalvm.util.Equivalence; 59 60 import jdk.vm.ci.meta.ConstantReflectionProvider; 61 import jdk.vm.ci.meta.DeoptimizationAction; 62 import jdk.vm.ci.meta.DeoptimizationReason; 63 import jdk.vm.ci.meta.JavaKind; 64 import jdk.vm.ci.meta.JavaTypeProfile.ProfiledType; 65 import jdk.vm.ci.meta.ResolvedJavaMethod; 66 import jdk.vm.ci.meta.ResolvedJavaType; 67 68 /** 69 * Polymorphic inlining of m methods with n type checks (n ≥ m) in case that the profiling 70 * information suggests a reasonable amount of different receiver types and different methods. If an 71 * unknown type is encountered a deoptimization is triggered. 72 */ 73 public class MultiTypeGuardInlineInfo extends AbstractInlineInfo { 74 75 private final List<ResolvedJavaMethod> concretes; 76 private final double[] methodProbabilities; 77 private final double maximumMethodProbability; 78 private final ArrayList<Integer> typesToConcretes; 79 private final ArrayList<ProfiledType> ptypes; 80 private final double notRecordedTypeProbability; 81 private final Inlineable[] inlineableElements; 82 83 public MultiTypeGuardInlineInfo(Invoke invoke, ArrayList<ResolvedJavaMethod> concretes, ArrayList<ProfiledType> ptypes, ArrayList<Integer> typesToConcretes, double notRecordedTypeProbability) { 84 super(invoke); 85 assert concretes.size() > 0 : "must have at least one method"; 86 assert ptypes.size() == typesToConcretes.size() : "array lengths must match"; 87 88 this.concretes = concretes; 89 this.ptypes = ptypes; 90 this.typesToConcretes = typesToConcretes; 91 this.notRecordedTypeProbability = notRecordedTypeProbability; 92 this.inlineableElements = new Inlineable[concretes.size()]; 93 this.methodProbabilities = computeMethodProbabilities(); 94 this.maximumMethodProbability = maximumMethodProbability(); 95 assert maximumMethodProbability > 0; 96 assert assertUniqueTypes(ptypes); 97 } 98 99 private static boolean assertUniqueTypes(ArrayList<ProfiledType> ptypes) { 100 EconomicSet<ResolvedJavaType> set = EconomicSet.create(Equivalence.DEFAULT); 101 for (ProfiledType ptype : ptypes) { 102 set.add(ptype.getType()); 103 } 104 return set.size() == ptypes.size(); 105 } 106 107 private double[] computeMethodProbabilities() { 108 double[] result = new double[concretes.size()]; 109 for (int i = 0; i < typesToConcretes.size(); i++) { 110 int concrete = typesToConcretes.get(i); 111 double probability = ptypes.get(i).getProbability(); 112 result[concrete] += probability; 113 } 114 return result; 115 } 116 117 private double maximumMethodProbability() { 118 double max = 0; 119 for (int i = 0; i < methodProbabilities.length; i++) { 120 max = Math.max(max, methodProbabilities[i]); 121 } 122 return max; 123 } 124 125 @Override 126 public int numberOfMethods() { 127 return concretes.size(); 128 } 129 130 @Override 131 public ResolvedJavaMethod methodAt(int index) { 132 assert index >= 0 && index < concretes.size(); 133 return concretes.get(index); 134 } 135 136 @Override 137 public Inlineable inlineableElementAt(int index) { 138 assert index >= 0 && index < concretes.size(); 139 return inlineableElements[index]; 140 } 141 142 @Override 143 public double probabilityAt(int index) { 144 return methodProbabilities[index]; 145 } 146 147 @Override 148 public double relevanceAt(int index) { 149 return probabilityAt(index) / maximumMethodProbability; 150 } 151 152 @Override 153 public void setInlinableElement(int index, Inlineable inlineableElement) { 154 assert index >= 0 && index < concretes.size(); 155 inlineableElements[index] = inlineableElement; 156 } 157 158 @Override 159 public EconomicSet<Node> inline(Providers providers) { 160 if (hasSingleMethod()) { 161 return inlineSingleMethod(graph(), providers.getStampProvider(), providers.getConstantReflection()); 162 } else { 163 return inlineMultipleMethods(graph(), providers); 164 } 165 } 166 167 @Override 168 public boolean shouldInline() { 169 for (ResolvedJavaMethod method : concretes) { 170 if (method.shouldBeInlined()) { 171 return true; 172 } 173 } 174 return false; 175 } 176 177 private boolean hasSingleMethod() { 178 return concretes.size() == 1 && !shouldFallbackToInvoke(); 179 } 180 181 private boolean shouldFallbackToInvoke() { 182 return notRecordedTypeProbability > 0; 183 } 184 185 private EconomicSet<Node> inlineMultipleMethods(StructuredGraph graph, Providers providers) { 186 int numberOfMethods = concretes.size(); 187 FixedNode continuation = invoke.next(); 188 189 // setup merge and phi nodes for results and exceptions 190 AbstractMergeNode returnMerge = graph.add(new MergeNode()); 191 returnMerge.setStateAfter(invoke.stateAfter()); 192 193 PhiNode returnValuePhi = null; 194 if (invoke.asNode().getStackKind() != JavaKind.Void) { 195 returnValuePhi = graph.addWithoutUnique(new ValuePhiNode(invoke.asNode().stamp().unrestricted(), returnMerge)); 196 } 197 198 AbstractMergeNode exceptionMerge = null; 199 PhiNode exceptionObjectPhi = null; 200 if (invoke instanceof InvokeWithExceptionNode) { 201 InvokeWithExceptionNode invokeWithException = (InvokeWithExceptionNode) invoke; 202 ExceptionObjectNode exceptionEdge = (ExceptionObjectNode) invokeWithException.exceptionEdge(); 203 204 exceptionMerge = graph.add(new MergeNode()); 205 206 FixedNode exceptionSux = exceptionEdge.next(); 207 graph.addBeforeFixed(exceptionSux, exceptionMerge); 208 exceptionObjectPhi = graph.addWithoutUnique(new ValuePhiNode(StampFactory.forKind(JavaKind.Object), exceptionMerge)); 209 exceptionMerge.setStateAfter(exceptionEdge.stateAfter().duplicateModified(invoke.stateAfter().bci, true, JavaKind.Object, new JavaKind[]{JavaKind.Object}, 210 new ValueNode[]{exceptionObjectPhi})); 211 } 212 213 // create one separate block for each invoked method 214 AbstractBeginNode[] successors = new AbstractBeginNode[numberOfMethods + 1]; 215 for (int i = 0; i < numberOfMethods; i++) { 216 successors[i] = createInvocationBlock(graph, invoke, returnMerge, returnValuePhi, exceptionMerge, exceptionObjectPhi, true); 217 } 218 219 // create the successor for an unknown type 220 FixedNode unknownTypeSux; 221 if (shouldFallbackToInvoke()) { 222 unknownTypeSux = createInvocationBlock(graph, invoke, returnMerge, returnValuePhi, exceptionMerge, exceptionObjectPhi, false); 223 } else { 224 unknownTypeSux = graph.add(new DeoptimizeNode(DeoptimizationAction.InvalidateReprofile, DeoptimizationReason.TypeCheckedInliningViolated)); 225 } 226 successors[successors.length - 1] = BeginNode.begin(unknownTypeSux); 227 228 // replace the invoke exception edge 229 if (invoke instanceof InvokeWithExceptionNode) { 230 InvokeWithExceptionNode invokeWithExceptionNode = (InvokeWithExceptionNode) invoke; 231 ExceptionObjectNode exceptionEdge = (ExceptionObjectNode) invokeWithExceptionNode.exceptionEdge(); 232 exceptionEdge.replaceAtUsages(exceptionObjectPhi); 233 exceptionEdge.setNext(null); 234 GraphUtil.killCFG(invokeWithExceptionNode.exceptionEdge()); 235 } 236 237 assert invoke.asNode().isAlive(); 238 239 // replace the invoke with a switch on the type of the actual receiver 240 boolean methodDispatch = createDispatchOnTypeBeforeInvoke(graph, successors, false, providers.getStampProvider(), providers.getConstantReflection()); 241 242 assert invoke.next() == continuation; 243 invoke.setNext(null); 244 returnMerge.setNext(continuation); 245 if (returnValuePhi != null) { 246 invoke.asNode().replaceAtUsages(returnValuePhi); 247 } 248 invoke.asNode().safeDelete(); 249 250 ArrayList<PiNode> replacementNodes = new ArrayList<>(); 251 252 // prepare the anchors for the invokes 253 for (int i = 0; i < numberOfMethods; i++) { 254 AbstractBeginNode node = successors[i]; 255 Invoke invokeForInlining = (Invoke) node.next(); 256 257 ResolvedJavaType commonType; 258 if (methodDispatch) { 259 commonType = concretes.get(i).getDeclaringClass(); 260 } else { 261 commonType = getLeastCommonType(i); 262 } 263 264 ValueNode receiver = ((MethodCallTargetNode) invokeForInlining.callTarget()).receiver(); 265 boolean exact = (getTypeCount(i) == 1 && !methodDispatch); 266 PiNode anchoredReceiver = InliningUtil.createAnchoredReceiver(graph, node, commonType, receiver, exact); 267 invokeForInlining.callTarget().replaceFirstInput(receiver, anchoredReceiver); 268 269 assert !anchoredReceiver.isDeleted() : anchoredReceiver; 270 replacementNodes.add(anchoredReceiver); 271 } 272 if (shouldFallbackToInvoke()) { 273 replacementNodes.add(null); 274 } 275 276 EconomicSet<Node> canonicalizeNodes = EconomicSet.create(Equivalence.DEFAULT); 277 // do the actual inlining for every invoke 278 for (int i = 0; i < numberOfMethods; i++) { 279 Invoke invokeForInlining = (Invoke) successors[i].next(); 280 canonicalizeNodes.addAll(doInline(i, invokeForInlining)); 281 } 282 if (returnValuePhi != null) { 283 canonicalizeNodes.add(returnValuePhi); 284 } 285 return canonicalizeNodes; 286 } 287 288 protected EconomicSet<Node> doInline(int index, Invoke invokeForInlining) { 289 return inline(invokeForInlining, methodAt(index), inlineableElementAt(index), false); 290 } 291 292 private int getTypeCount(int concreteMethodIndex) { 293 int count = 0; 294 for (int i = 0; i < typesToConcretes.size(); i++) { 295 if (typesToConcretes.get(i) == concreteMethodIndex) { 296 count++; 297 } 298 } 299 return count; 300 } 301 302 private ResolvedJavaType getLeastCommonType(int concreteMethodIndex) { 303 ResolvedJavaType commonType = null; 304 for (int i = 0; i < typesToConcretes.size(); i++) { 305 if (typesToConcretes.get(i) == concreteMethodIndex) { 306 if (commonType == null) { 307 commonType = ptypes.get(i).getType(); 308 } else { 309 commonType = commonType.findLeastCommonAncestor(ptypes.get(i).getType()); 310 } 311 } 312 } 313 assert commonType != null; 314 return commonType; 315 } 316 317 private ResolvedJavaType getLeastCommonType() { 318 ResolvedJavaType result = getLeastCommonType(0); 319 for (int i = 1; i < concretes.size(); i++) { 320 result = result.findLeastCommonAncestor(getLeastCommonType(i)); 321 } 322 return result; 323 } 324 325 private EconomicSet<Node> inlineSingleMethod(StructuredGraph graph, StampProvider stampProvider, ConstantReflectionProvider constantReflection) { 326 assert concretes.size() == 1 && inlineableElements.length == 1 && ptypes.size() > 1 && !shouldFallbackToInvoke() && notRecordedTypeProbability == 0; 327 328 AbstractBeginNode calleeEntryNode = graph.add(new BeginNode()); 329 330 AbstractBeginNode unknownTypeSux = createUnknownTypeSuccessor(graph); 331 AbstractBeginNode[] successors = new AbstractBeginNode[]{calleeEntryNode, unknownTypeSux}; 332 createDispatchOnTypeBeforeInvoke(graph, successors, false, stampProvider, constantReflection); 333 334 calleeEntryNode.setNext(invoke.asNode()); 335 336 return inline(invoke, methodAt(0), inlineableElementAt(0), false); 337 } 338 339 private boolean createDispatchOnTypeBeforeInvoke(StructuredGraph graph, AbstractBeginNode[] successors, boolean invokeIsOnlySuccessor, StampProvider stampProvider, 340 ConstantReflectionProvider constantReflection) { 341 assert ptypes.size() >= 1; 342 ValueNode nonNullReceiver = InliningUtil.nonNullReceiver(invoke); 343 LoadHubNode hub = graph.unique(new LoadHubNode(stampProvider, nonNullReceiver)); 344 345 Debug.log("Type switch with %d types", concretes.size()); 346 347 ResolvedJavaType[] keys = new ResolvedJavaType[ptypes.size()]; 348 double[] keyProbabilities = new double[ptypes.size() + 1]; 349 int[] keySuccessors = new int[ptypes.size() + 1]; 350 double totalProbability = notRecordedTypeProbability; 351 for (int i = 0; i < ptypes.size(); i++) { 352 keys[i] = ptypes.get(i).getType(); 353 keyProbabilities[i] = ptypes.get(i).getProbability(); 354 totalProbability += keyProbabilities[i]; 355 keySuccessors[i] = invokeIsOnlySuccessor ? 0 : typesToConcretes.get(i); 356 assert keySuccessors[i] < successors.length - 1 : "last successor is the unknownTypeSux"; 357 } 358 keyProbabilities[keyProbabilities.length - 1] = notRecordedTypeProbability; 359 keySuccessors[keySuccessors.length - 1] = successors.length - 1; 360 361 // Normalize the probabilities. 362 for (int i = 0; i < keyProbabilities.length; i++) { 363 keyProbabilities[i] /= totalProbability; 364 } 365 366 TypeSwitchNode typeSwitch = graph.add(new TypeSwitchNode(hub, successors, keys, keyProbabilities, keySuccessors, constantReflection)); 367 FixedWithNextNode pred = (FixedWithNextNode) invoke.asNode().predecessor(); 368 pred.setNext(typeSwitch); 369 return false; 370 } 371 372 private static AbstractBeginNode createInvocationBlock(StructuredGraph graph, Invoke invoke, AbstractMergeNode returnMerge, PhiNode returnValuePhi, AbstractMergeNode exceptionMerge, 373 PhiNode exceptionObjectPhi, boolean useForInlining) { 374 Invoke duplicatedInvoke = duplicateInvokeForInlining(graph, invoke, exceptionMerge, exceptionObjectPhi, useForInlining); 375 AbstractBeginNode calleeEntryNode = graph.add(new BeginNode()); 376 calleeEntryNode.setNext(duplicatedInvoke.asNode()); 377 378 EndNode endNode = graph.add(new EndNode()); 379 duplicatedInvoke.setNext(endNode); 380 returnMerge.addForwardEnd(endNode); 381 382 if (returnValuePhi != null) { 383 returnValuePhi.addInput(duplicatedInvoke.asNode()); 384 } 385 return calleeEntryNode; 386 } 387 388 private static Invoke duplicateInvokeForInlining(StructuredGraph graph, Invoke invoke, AbstractMergeNode exceptionMerge, PhiNode exceptionObjectPhi, boolean useForInlining) { 389 Invoke result = (Invoke) invoke.asNode().copyWithInputs(); 390 Node callTarget = result.callTarget().copyWithInputs(); 391 result.asNode().replaceFirstInput(result.callTarget(), callTarget); 392 result.setUseForInlining(useForInlining); 393 394 JavaKind kind = invoke.asNode().getStackKind(); 395 if (kind != JavaKind.Void) { 396 FrameState stateAfter = invoke.stateAfter(); 397 stateAfter = stateAfter.duplicate(stateAfter.bci); 398 stateAfter.replaceFirstInput(invoke.asNode(), result.asNode()); 399 result.setStateAfter(stateAfter); 400 } 401 402 if (invoke instanceof InvokeWithExceptionNode) { 403 assert exceptionMerge != null && exceptionObjectPhi != null; 404 405 InvokeWithExceptionNode invokeWithException = (InvokeWithExceptionNode) invoke; 406 ExceptionObjectNode exceptionEdge = (ExceptionObjectNode) invokeWithException.exceptionEdge(); 407 FrameState stateAfterException = exceptionEdge.stateAfter(); 408 409 ExceptionObjectNode newExceptionEdge = (ExceptionObjectNode) exceptionEdge.copyWithInputs(); 410 // set new state (pop old exception object, push new one) 411 newExceptionEdge.setStateAfter(stateAfterException.duplicateModified(JavaKind.Object, JavaKind.Object, newExceptionEdge)); 412 413 EndNode endNode = graph.add(new EndNode()); 414 newExceptionEdge.setNext(endNode); 415 exceptionMerge.addForwardEnd(endNode); 416 exceptionObjectPhi.addInput(newExceptionEdge); 417 418 ((InvokeWithExceptionNode) result).setExceptionEdge(newExceptionEdge); 419 } 420 return result; 421 } 422 423 @Override 424 public void tryToDevirtualizeInvoke(Providers providers) { 425 if (hasSingleMethod()) { 426 devirtualizeWithTypeSwitch(graph(), InvokeKind.Special, concretes.get(0), providers.getStampProvider(), providers.getConstantReflection()); 427 } else { 428 tryToDevirtualizeMultipleMethods(graph(), providers.getStampProvider(), providers.getConstantReflection()); 429 } 430 } 431 432 private void tryToDevirtualizeMultipleMethods(StructuredGraph graph, StampProvider stampProvider, ConstantReflectionProvider constantReflection) { 433 MethodCallTargetNode methodCallTarget = (MethodCallTargetNode) invoke.callTarget(); 434 if (methodCallTarget.invokeKind() == InvokeKind.Interface) { 435 ResolvedJavaMethod targetMethod = methodCallTarget.targetMethod(); 436 ResolvedJavaType leastCommonType = getLeastCommonType(); 437 ResolvedJavaType contextType = invoke.getContextType(); 438 // check if we have a common base type that implements the interface -> in that case 439 // we have a vtable entry for the interface method and can use a less expensive 440 // virtual call 441 if (!leastCommonType.isInterface() && targetMethod.getDeclaringClass().isAssignableFrom(leastCommonType)) { 442 ResolvedJavaMethod baseClassTargetMethod = leastCommonType.resolveConcreteMethod(targetMethod, contextType); 443 if (baseClassTargetMethod != null) { 444 devirtualizeWithTypeSwitch(graph, InvokeKind.Virtual, leastCommonType.resolveConcreteMethod(targetMethod, contextType), stampProvider, constantReflection); 445 } 446 } 447 } 448 } 449 450 private void devirtualizeWithTypeSwitch(StructuredGraph graph, InvokeKind kind, ResolvedJavaMethod target, StampProvider stampProvider, ConstantReflectionProvider constantReflection) { 451 AbstractBeginNode invocationEntry = graph.add(new BeginNode()); 452 AbstractBeginNode unknownTypeSux = createUnknownTypeSuccessor(graph); 453 AbstractBeginNode[] successors = new AbstractBeginNode[]{invocationEntry, unknownTypeSux}; 454 createDispatchOnTypeBeforeInvoke(graph, successors, true, stampProvider, constantReflection); 455 456 invocationEntry.setNext(invoke.asNode()); 457 ValueNode receiver = ((MethodCallTargetNode) invoke.callTarget()).receiver(); 458 PiNode anchoredReceiver = InliningUtil.createAnchoredReceiver(graph, invocationEntry, target.getDeclaringClass(), receiver, false); 459 invoke.callTarget().replaceFirstInput(receiver, anchoredReceiver); 460 InliningUtil.replaceInvokeCallTarget(invoke, graph, kind, target); 461 } 462 463 private static AbstractBeginNode createUnknownTypeSuccessor(StructuredGraph graph) { 464 return BeginNode.begin(graph.add(new DeoptimizeNode(DeoptimizationAction.InvalidateReprofile, DeoptimizationReason.TypeCheckedInliningViolated))); 465 } 466 467 @Override 468 public String toString() { 469 StringBuilder builder = new StringBuilder(shouldFallbackToInvoke() ? "megamorphic" : "polymorphic"); 470 builder.append(", "); 471 builder.append(concretes.size()); 472 builder.append(" methods [ "); 473 for (int i = 0; i < concretes.size(); i++) { 474 builder.append(concretes.get(i).format(" %H.%n(%p):%r")); 475 } 476 builder.append(" ], "); 477 builder.append(ptypes.size()); 478 builder.append(" type checks [ "); 479 for (int i = 0; i < ptypes.size(); i++) { 480 builder.append(" "); 481 builder.append(ptypes.get(i).getType().getName()); 482 builder.append(ptypes.get(i).getProbability()); 483 } 484 builder.append(" ]"); 485 return builder.toString(); 486 } 487 }