001/* 002 * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. 008 * 009 * This code is distributed in the hope that it will be useful, but WITHOUT 010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 011 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 012 * version 2 for more details (a copy is included in the LICENSE file that 013 * accompanied this code). 014 * 015 * You should have received a copy of the GNU General Public License version 016 * 2 along with this work; if not, write to the Free Software Foundation, 017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 018 * 019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 020 * or visit www.oracle.com if you need additional information or have any 021 * questions. 022 */ 023package com.oracle.graal.truffle; 024 025import static com.oracle.graal.truffle.TruffleCompilerOptions.*; 026 027import java.io.*; 028import java.lang.reflect.*; 029import java.util.*; 030import java.util.concurrent.atomic.*; 031import java.util.stream.*; 032 033import jdk.internal.jvmci.code.*; 034import jdk.internal.jvmci.common.*; 035import jdk.internal.jvmci.meta.*; 036 037import com.oracle.graal.truffle.debug.*; 038import com.oracle.truffle.api.*; 039import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; 040import com.oracle.truffle.api.frame.*; 041import com.oracle.truffle.api.impl.*; 042import com.oracle.truffle.api.nodes.*; 043import com.oracle.truffle.api.nodes.Node; 044import com.oracle.truffle.api.utilities.*; 045 046/** 047 * Call target that is optimized by Graal upon surpassing a specific invocation threshold. 048 */ 049public class OptimizedCallTarget extends InstalledCode implements RootCallTarget, LoopCountReceiver, ReplaceObserver { 050 051 protected final GraalTruffleRuntime runtime; 052 private SpeculationLog speculationLog; 053 protected final CompilationProfile compilationProfile; 054 protected final CompilationPolicy compilationPolicy; 055 private final OptimizedCallTarget sourceCallTarget; 056 private final AtomicInteger callSitesKnown = new AtomicInteger(0); 057 private final ValueProfile exceptionProfile = ValueProfile.createClassProfile(); 058 059 @CompilationFinal private Class<?>[] profiledArgumentTypes; 060 @CompilationFinal private Assumption profiledArgumentTypesAssumption; 061 @CompilationFinal private Class<?> profiledReturnType; 062 @CompilationFinal private Assumption profiledReturnTypeAssumption; 063 064 private final RootNode uninitializedRootNode; 065 private final RootNode rootNode; 066 067 /* Experimental fields for new splitting. */ 068 private final Map<TruffleStamp, OptimizedCallTarget> splitVersions = new HashMap<>(); 069 private TruffleStamp argumentStamp = DefaultTruffleStamp.getInstance(); 070 071 private TruffleInlining inlining; 072 private int cachedNonTrivialNodeCount = -1; 073 private boolean compiling; 074 075 /** 076 * When this call target is inlined, the inlining {@link InstalledCode} registers this 077 * assumption. It gets invalidated when a node rewriting is performed. This ensures that all 078 * compiled methods that have this call target inlined are properly invalidated. 079 */ 080 private final CyclicAssumption nodeRewritingAssumption; 081 082 public final RootNode getRootNode() { 083 return rootNode; 084 } 085 086 public OptimizedCallTarget(OptimizedCallTarget sourceCallTarget, RootNode rootNode, GraalTruffleRuntime runtime, CompilationPolicy compilationPolicy, SpeculationLog speculationLog) { 087 super(rootNode.toString()); 088 this.sourceCallTarget = sourceCallTarget; 089 this.runtime = runtime; 090 this.speculationLog = speculationLog; 091 this.rootNode = rootNode; 092 this.compilationPolicy = compilationPolicy; 093 this.rootNode.adoptChildren(); 094 this.rootNode.applyInstrumentation(); 095 this.uninitializedRootNode = sourceCallTarget == null ? cloneRootNode(rootNode) : sourceCallTarget.uninitializedRootNode; 096 if (TruffleCallTargetProfiling.getValue()) { 097 this.compilationProfile = new TraceCompilationProfile(); 098 } else { 099 this.compilationProfile = new CompilationProfile(); 100 } 101 this.nodeRewritingAssumption = new CyclicAssumption("nodeRewritingAssumption of " + rootNode.toString()); 102 } 103 104 public final void log(String message) { 105 runtime.log(message); 106 } 107 108 public final boolean isCompiling() { 109 return compiling; 110 } 111 112 private static RootNode cloneRootNode(RootNode root) { 113 if (root == null || !root.isCloningAllowed()) { 114 return null; 115 } 116 return NodeUtil.cloneNode(root); 117 } 118 119 public Assumption getNodeRewritingAssumption() { 120 return nodeRewritingAssumption.getAssumption(); 121 } 122 123 public final void mergeArgumentStamp(TruffleStamp p) { 124 this.argumentStamp = this.argumentStamp.join(p); 125 } 126 127 public final TruffleStamp getArgumentStamp() { 128 return argumentStamp; 129 } 130 131 private int cloneIndex; 132 133 public int getCloneIndex() { 134 return cloneIndex; 135 } 136 137 public OptimizedCallTarget cloneUninitialized() { 138 RootNode copiedRoot = cloneRootNode(uninitializedRootNode); 139 if (copiedRoot == null) { 140 return null; 141 } 142 OptimizedCallTarget splitTarget = (OptimizedCallTarget) runtime.createClonedCallTarget(this, copiedRoot); 143 splitTarget.cloneIndex = cloneIndex++; 144 return splitTarget; 145 } 146 147 public Map<TruffleStamp, OptimizedCallTarget> getSplitVersions() { 148 return splitVersions; 149 } 150 151 public SpeculationLog getSpeculationLog() { 152 return speculationLog; 153 } 154 155 @Override 156 public Object call(Object... args) { 157 compilationProfile.reportIndirectCall(); 158 if (profiledArgumentTypesAssumption != null && profiledArgumentTypesAssumption.isValid()) { 159 CompilerDirectives.transferToInterpreterAndInvalidate(); 160 profiledArgumentTypesAssumption.invalidate(); 161 profiledArgumentTypes = null; 162 } 163 return doInvoke(args); 164 } 165 166 public final Object callDirect(Object... args) { 167 compilationProfile.reportDirectCall(); 168 profileArguments(args); 169 try { 170 Object result = doInvoke(args); 171 Class<?> klass = profiledReturnType; 172 if (klass != null && CompilerDirectives.inCompiledCode() && profiledReturnTypeAssumption.isValid()) { 173 result = FrameWithoutBoxing.unsafeCast(result, klass, true, true); 174 } 175 return result; 176 } catch (Throwable t) { 177 t = exceptionProfile.profile(t); 178 if (t instanceof RuntimeException) { 179 throw (RuntimeException) t; 180 } else if (t instanceof Error) { 181 throw (Error) t; 182 } else { 183 CompilerDirectives.transferToInterpreter(); 184 throw new RuntimeException(t); 185 } 186 } 187 } 188 189 public final Object callInlined(Object... arguments) { 190 compilationProfile.reportInlinedCall(); 191 VirtualFrame frame = createFrame(getRootNode().getFrameDescriptor(), arguments); 192 return callProxy(frame); 193 } 194 195 @ExplodeLoop 196 void profileArguments(Object[] args) { 197 if (profiledArgumentTypesAssumption == null) { 198 CompilerDirectives.transferToInterpreterAndInvalidate(); 199 initializeProfiledArgumentTypes(args); 200 } else if (profiledArgumentTypes != null) { 201 if (profiledArgumentTypes.length != args.length) { 202 CompilerDirectives.transferToInterpreterAndInvalidate(); 203 profiledArgumentTypesAssumption.invalidate(); 204 profiledArgumentTypes = null; 205 } else if (TruffleArgumentTypeSpeculation.getValue() && profiledArgumentTypesAssumption.isValid()) { 206 for (int i = 0; i < profiledArgumentTypes.length; i++) { 207 if (profiledArgumentTypes[i] != null && !profiledArgumentTypes[i].isInstance(args[i])) { 208 CompilerDirectives.transferToInterpreterAndInvalidate(); 209 updateProfiledArgumentTypes(args); 210 break; 211 } 212 } 213 } 214 } 215 } 216 217 private void initializeProfiledArgumentTypes(Object[] args) { 218 CompilerAsserts.neverPartOfCompilation(); 219 profiledArgumentTypesAssumption = Truffle.getRuntime().createAssumption("Profiled Argument Types"); 220 profiledArgumentTypes = new Class<?>[args.length]; 221 if (TruffleArgumentTypeSpeculation.getValue()) { 222 for (int i = 0; i < args.length; i++) { 223 profiledArgumentTypes[i] = classOf(args[i]); 224 } 225 } 226 } 227 228 private void updateProfiledArgumentTypes(Object[] args) { 229 CompilerAsserts.neverPartOfCompilation(); 230 profiledArgumentTypesAssumption.invalidate(); 231 for (int j = 0; j < profiledArgumentTypes.length; j++) { 232 profiledArgumentTypes[j] = joinTypes(profiledArgumentTypes[j], classOf(args[j])); 233 } 234 profiledArgumentTypesAssumption = Truffle.getRuntime().createAssumption("Profiled Argument Types"); 235 } 236 237 private static Class<?> classOf(Object arg) { 238 return arg != null ? arg.getClass() : null; 239 } 240 241 private static Class<?> joinTypes(Class<?> class1, Class<?> class2) { 242 if (class1 == class2) { 243 return class1; 244 } else if (class1 == null || class2 == null) { 245 return null; 246 } else if (class1.isAssignableFrom(class2)) { 247 return class1; 248 } else if (class2.isAssignableFrom(class1)) { 249 return class2; 250 } else { 251 return Object.class; 252 } 253 } 254 255 protected Object doInvoke(Object[] args) { 256 return callBoundary(args); 257 } 258 259 @TruffleCallBoundary 260 protected final Object callBoundary(Object[] args) { 261 if (CompilerDirectives.inInterpreter()) { 262 // We are called and we are still in Truffle interpreter mode. 263 interpreterCall(); 264 } else { 265 // We come here from compiled code 266 } 267 268 return callRoot(args); 269 } 270 271 public final Object callRoot(Object[] originalArguments) { 272 Object[] args = originalArguments; 273 if (this.profiledArgumentTypesAssumption != null && CompilerDirectives.inCompiledCode() && profiledArgumentTypesAssumption.isValid()) { 274 args = FrameWithoutBoxing.unsafeCast(castArrayFixedLength(args, profiledArgumentTypes.length), Object[].class, true, true); 275 if (TruffleArgumentTypeSpeculation.getValue()) { 276 args = castArguments(args); 277 } 278 } 279 280 VirtualFrame frame = createFrame(getRootNode().getFrameDescriptor(), args); 281 Object result = callProxy(frame); 282 283 profileReturnType(result); 284 285 return result; 286 } 287 288 void profileReturnType(Object result) { 289 if (profiledReturnTypeAssumption == null) { 290 if (TruffleReturnTypeSpeculation.getValue()) { 291 CompilerDirectives.transferToInterpreterAndInvalidate(); 292 profiledReturnType = (result == null ? null : result.getClass()); 293 profiledReturnTypeAssumption = Truffle.getRuntime().createAssumption("Profiled Return Type"); 294 } 295 } else if (profiledReturnType != null) { 296 if (result == null || profiledReturnType != result.getClass()) { 297 CompilerDirectives.transferToInterpreterAndInvalidate(); 298 profiledReturnType = null; 299 profiledReturnTypeAssumption.invalidate(); 300 } 301 } 302 } 303 304 @Override 305 public void invalidate() { 306 invalidate(null, null); 307 } 308 309 protected void invalidate(Object source, CharSequence reason) { 310 if (isValid()) { 311 this.runtime.invalidateInstalledCode(this, source, reason); 312 } 313 cachedNonTrivialNodeCount = -1; 314 } 315 316 public TruffleInlining getInlining() { 317 return inlining; 318 } 319 320 public void setInlining(TruffleInlining inliningDecision) { 321 this.inlining = inliningDecision; 322 } 323 324 private boolean cancelInstalledTask(Node source, CharSequence reason) { 325 return this.runtime.cancelInstalledTask(this, source, reason); 326 } 327 328 private void interpreterCall() { 329 if (isValid()) { 330 // Stubs were deoptimized => reinstall. 331 this.runtime.reinstallStubs(); 332 } else { 333 compilationProfile.reportInterpreterCall(); 334 if (!isCompiling() && compilationPolicy.shouldCompile(compilationProfile, getCompilerOptions())) { 335 compile(); 336 } 337 } 338 } 339 340 public void compile() { 341 if (!isCompiling()) { 342 compiling = true; 343 runtime.compile(this, TruffleBackgroundCompilation.getValue() && !TruffleCompilationExceptionsAreThrown.getValue()); 344 } 345 } 346 347 public void notifyCompilationFailed(Throwable t) { 348 if (t instanceof BailoutException && !((BailoutException) t).isPermanent()) { 349 /* 350 * Non permanent bailouts are expected cases. A non permanent bailout would be for 351 * example class redefinition during code installation. As opposed to permanent 352 * bailouts, non permanent bailouts will trigger recompilation and are not considered a 353 * failure state. 354 */ 355 } else { 356 compilationPolicy.recordCompilationFailure(t); 357 if (TruffleCompilationExceptionsAreThrown.getValue()) { 358 throw new OptimizationFailedException(t, this); 359 } 360 if (TruffleCompilationExceptionsAreFatal.getValue()) { 361 printException(t); 362 System.exit(-1); 363 } 364 } 365 } 366 367 private void printException(Throwable e) { 368 StringWriter string = new StringWriter(); 369 e.printStackTrace(new PrintWriter(string)); 370 log(string.toString()); 371 } 372 373 public void notifyCompilationFinished(boolean successful) { 374 if (successful && inlining != null) { 375 dequeueInlinedCallSites(inlining); 376 } 377 compiling = false; 378 } 379 380 private void dequeueInlinedCallSites(TruffleInlining parentDecision) { 381 for (TruffleInliningDecision decision : parentDecision) { 382 if (decision.isInline()) { 383 OptimizedCallTarget target = decision.getTarget(); 384 target.cancelInstalledTask(decision.getProfile().getCallNode(), "Inlining caller compiled."); 385 dequeueInlinedCallSites(decision); 386 } 387 } 388 } 389 390 protected final Object callProxy(VirtualFrame frame) { 391 try { 392 return getRootNode().execute(frame); 393 } finally { 394 // this assertion is needed to keep the values from being cleared as non-live locals 395 assert frame != null && this != null; 396 } 397 } 398 399 public final int getKnownCallSiteCount() { 400 return callSitesKnown.get(); 401 } 402 403 public final void incrementKnownCallSites() { 404 callSitesKnown.incrementAndGet(); 405 } 406 407 public final void decrementKnownCallSites() { 408 callSitesKnown.decrementAndGet(); 409 } 410 411 public final OptimizedCallTarget getSourceCallTarget() { 412 return sourceCallTarget; 413 } 414 415 @Override 416 public String toString() { 417 String superString = rootNode.toString(); 418 if (isValid()) { 419 superString += " <opt>"; 420 } 421 if (sourceCallTarget != null) { 422 superString += " <split-" + cloneIndex + "-" + argumentStamp.toStringShort() + ">"; 423 } 424 return superString; 425 } 426 427 public CompilationProfile getCompilationProfile() { 428 return compilationProfile; 429 } 430 431 @ExplodeLoop 432 private Object[] castArguments(Object[] originalArguments) { 433 Object[] castArguments = new Object[profiledArgumentTypes.length]; 434 for (int i = 0; i < profiledArgumentTypes.length; i++) { 435 castArguments[i] = profiledArgumentTypes[i] != null ? FrameWithoutBoxing.unsafeCast(originalArguments[i], profiledArgumentTypes[i], true, true) : originalArguments[i]; 436 } 437 return castArguments; 438 } 439 440 private static Object castArrayFixedLength(Object[] args, @SuppressWarnings("unused") int length) { 441 return args; 442 } 443 444 public static VirtualFrame createFrame(FrameDescriptor descriptor, Object[] args) { 445 if (TruffleCompilerOptions.TruffleUseFrameWithoutBoxing.getValue()) { 446 return new FrameWithoutBoxing(descriptor, args); 447 } else { 448 return new FrameWithBoxing(descriptor, args); 449 } 450 } 451 452 public List<OptimizedDirectCallNode> getCallNodes() { 453 final List<OptimizedDirectCallNode> callNodes = new ArrayList<>(); 454 getRootNode().accept(new NodeVisitor() { 455 public boolean visit(Node node) { 456 if (node instanceof OptimizedDirectCallNode) { 457 callNodes.add((OptimizedDirectCallNode) node); 458 } 459 return true; 460 } 461 }); 462 return callNodes; 463 } 464 465 @Override 466 public void reportLoopCount(int count) { 467 compilationProfile.reportLoopCount(count); 468 } 469 470 @Override 471 public boolean nodeReplaced(Node oldNode, Node newNode, CharSequence reason) { 472 CompilerAsserts.neverPartOfCompilation(); 473 if (isValid()) { 474 invalidate(newNode, reason); 475 } 476 /* Notify compiled method that have inlined this call target that the tree changed. */ 477 nodeRewritingAssumption.invalidate(); 478 479 compilationProfile.reportNodeReplaced(); 480 if (cancelInstalledTask(newNode, reason)) { 481 compilationProfile.reportInvalidated(); 482 } 483 return false; 484 } 485 486 public void accept(NodeVisitor visitor, boolean includeInlinedNodes) { 487 TruffleInlining inliner = getInlining(); 488 if (includeInlinedNodes && inliner != null) { 489 inlining.accept(this, visitor); 490 } else { 491 getRootNode().accept(visitor); 492 } 493 } 494 495 public Stream<Node> nodeStream(boolean includeInlinedNodes) { 496 Iterator<Node> iterator; 497 TruffleInlining inliner = getInlining(); 498 if (includeInlinedNodes && inliner != null) { 499 iterator = inliner.makeNodeIterator(this); 500 } else { 501 iterator = NodeUtil.makeRecursiveIterator(this.getRootNode()); 502 } 503 return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); 504 } 505 506 public final int getNonTrivialNodeCount() { 507 if (cachedNonTrivialNodeCount == -1) { 508 cachedNonTrivialNodeCount = calculateNonTrivialNodes(getRootNode()); 509 } 510 return cachedNonTrivialNodeCount; 511 } 512 513 public static int calculateNonTrivialNodes(Node node) { 514 NonTrivialNodeCountVisitor visitor = new NonTrivialNodeCountVisitor(); 515 node.accept(visitor); 516 return visitor.nodeCount; 517 } 518 519 public Map<String, Object> getDebugProperties() { 520 Map<String, Object> properties = new LinkedHashMap<>(); 521 AbstractDebugCompilationListener.addASTSizeProperty(this, properties); 522 properties.putAll(getCompilationProfile().getDebugProperties()); 523 return properties; 524 } 525 526 public static Method getCallDirectMethod() { 527 try { 528 return OptimizedCallTarget.class.getDeclaredMethod("callDirect", Object[].class); 529 } catch (NoSuchMethodException | SecurityException e) { 530 throw new JVMCIError(e); 531 } 532 } 533 534 public static Method getCallInlinedMethod() { 535 try { 536 return OptimizedCallTarget.class.getDeclaredMethod("callInlined", Object[].class); 537 } catch (NoSuchMethodException | SecurityException e) { 538 throw new JVMCIError(e); 539 } 540 } 541 542 private CompilerOptions getCompilerOptions() { 543 final ExecutionContext context = rootNode.getExecutionContext(); 544 545 if (context == null) { 546 return DefaultCompilerOptions.INSTANCE; 547 } 548 549 return context.getCompilerOptions(); 550 } 551 552 private static final class NonTrivialNodeCountVisitor implements NodeVisitor { 553 554 public int nodeCount; 555 556 public boolean visit(Node node) { 557 if (!node.getCost().isTrivial()) { 558 nodeCount++; 559 } 560 return true; 561 } 562 563 } 564 565}