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