diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DeoptInvalidateListener.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DeoptInvalidateListener.java index 742a153e1f1c..e65dd5025dbd 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DeoptInvalidateListener.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DeoptInvalidateListener.java @@ -45,7 +45,7 @@ protected DeoptInvalidateListener(OptimizedTruffleRuntime runtime, OptimizedCall } @Override - public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame) { + public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame, String reason) { if (target == focus) { deoptimized = true; } diff --git a/truffle/src/com.oracle.truffle.compiler/src/com/oracle/truffle/compiler/TruffleCompilerAssumptionDependency.java b/truffle/src/com.oracle.truffle.compiler/src/com/oracle/truffle/compiler/TruffleCompilerAssumptionDependency.java index 10883dc35999..def8e39d45a9 100644 --- a/truffle/src/com.oracle.truffle.compiler/src/com/oracle/truffle/compiler/TruffleCompilerAssumptionDependency.java +++ b/truffle/src/com.oracle.truffle.compiler/src/com/oracle/truffle/compiler/TruffleCompilerAssumptionDependency.java @@ -68,6 +68,8 @@ public void onAssumptionInvalidated(Object source, CharSequence reason) { boolean wasActive = false; InstalledCode code = getInstalledCode(); if (code != null && code.isAlive()) { + // No need to set deoptimize or invalidation reason here because the defaults, 'true' + // and 'JVMCI_INVALIDATE' are the appropriate. code.invalidate(); wasActive = true; } else { diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedCallTarget.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedCallTarget.java index a2b82c3220fd..86d929784165 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedCallTarget.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedCallTarget.java @@ -438,7 +438,7 @@ public final void prepareForCompilation() { } @Override - public final boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) { + public boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) { RootNode root = this.rootNode; if (root == null) { throw CompilerDirectives.shouldNotReachHere("Initialization call targets cannot be compiled."); @@ -548,6 +548,7 @@ public final RootNode getRootNode() { public final void resetCompilationProfile() { this.callCount = 0; this.callAndLoopCount = 0; + this.successfulCompilationsCount = 0; } @Override @@ -853,8 +854,8 @@ private RuntimeException handleException(VirtualFrame frame, Throwable t) { throw rethrow(profiledT); } - private void notifyDeoptimized(VirtualFrame frame) { - runtime().getListener().onCompilationDeoptimized(this, frame); + protected void notifyDeoptimized(VirtualFrame frame) { + runtime().getListener().onCompilationDeoptimized(this, frame, null); } protected static OptimizedTruffleRuntime runtime() { diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntimeListener.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntimeListener.java index ad34c5b95c64..ba50ad8d5cd5 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntimeListener.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntimeListener.java @@ -295,7 +295,28 @@ default void onCompilationInvalidated(OptimizedCallTarget target, Object source, * @param target the call target whose compiled code was just deoptimized * @param frame */ + @Deprecated(since = "26.0") default void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame) { + onCompilationDeoptimized(target, frame, null); + } + + /** + * Notifies this object when {@code target} has just deoptimized and is now executing in the + * Truffle interpreter instead of executing compiled code. + * + * @param target the call target whose compiled code was just deoptimized + * @param frame + * @param reason optional reason why the deoptimization happened. + */ + default void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame, String reason) { + } + + /** + * Notifies this object when {@code target} has its execution profile reset. + * + * @param target the call target whose profile was just reset. + */ + default void onProfileReset(OptimizedCallTarget target) { } /** diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntimeListenerDispatcher.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntimeListenerDispatcher.java index 3714964b3d38..a657e676a03e 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntimeListenerDispatcher.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntimeListenerDispatcher.java @@ -115,8 +115,13 @@ public void onCompilationInvalidated(OptimizedCallTarget target, Object source, } @Override - public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame) { - invokeListeners((l) -> l.onCompilationDeoptimized(target, frame)); + public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame, String reason) { + invokeListeners((l) -> l.onCompilationDeoptimized(target, frame, reason)); + } + + @Override + public void onProfileReset(OptimizedCallTarget target) { + invokeListeners((l) -> l.onProfileReset(target)); } @Override diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/debug/JFRListener.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/debug/JFRListener.java index 9dc2cea1b688..33177af66f83 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/debug/JFRListener.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/debug/JFRListener.java @@ -127,7 +127,7 @@ public void onCompilationStarted(OptimizedCallTarget target, AbstractCompilation } @Override - public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame) { + public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame, String reason) { DeoptimizationEvent event = FACTORY.createDeoptimizationEvent(); if (event.isEnabled()) { event.setRootFunction(target); diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/debug/TraceCompilationListener.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/debug/TraceCompilationListener.java index 0988dea5e7b3..9349a755a14f 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/debug/TraceCompilationListener.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/debug/TraceCompilationListener.java @@ -104,6 +104,7 @@ public static void install(OptimizedTruffleRuntime runtime) { private static final String DEOPT_PADDING = " "; private static final String INV_FORMAT = "opt inval. " + TARGET_FORMAT + " " + INV_PADDING + "|UTC %s|Src %s|Reason %s"; private static final String DEOPT_FORMAT = "opt deopt " + TARGET_FORMAT + "|Invalidated %5b|" + DEOPT_PADDING + "|UTC %s|Src %s"; + private static final String REPROF_FORMAT = "opt reprof " + TARGET_FORMAT + "|" + INV_PADDING + "|UTC %s|Src %s"; // @formatter:on @Override @@ -223,7 +224,7 @@ private void log(OptimizedCallTarget target, String message) { } @Override - public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame) { + public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame, String reason) { if (target.engine.traceCompilation || target.engine.traceCompilationDetails) { log(target, String.format(DEOPT_FORMAT, target.engineId(), @@ -231,6 +232,19 @@ public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame) { safeTargetName(target), !target.isValid(), TIME_FORMATTER.format(ZonedDateTime.now()), + formatSourceSection(safeSourceSection(target)), + reason != null ? reason : "unknown")); + } + } + + @Override + public void onProfileReset(OptimizedCallTarget target) { + if (target.engine.traceCompilation || target.engine.traceCompilationDetails) { + log(target, String.format(REPROF_FORMAT, + target.engineId(), + target.id, + safeTargetName(target), + TIME_FORMATTER.format(ZonedDateTime.now()), formatSourceSection(safeSourceSection(target)))); } } diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/hotspot/HotSpotOptimizedCallTarget.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/hotspot/HotSpotOptimizedCallTarget.java index b0b1eca6a8ee..a975c0e93f21 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/hotspot/HotSpotOptimizedCallTarget.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/hotspot/HotSpotOptimizedCallTarget.java @@ -42,6 +42,7 @@ import java.lang.reflect.Method; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.compiler.TruffleCompiler; import com.oracle.truffle.runtime.EngineData; @@ -97,24 +98,38 @@ public HotSpotOptimizedCallTarget(EngineData engine) { private static final Method setSpeculationLog; /** - * Reflective reference to {@code InstalledCode.invalidate(boolean deoptimize)} so that this + * Reflective reference to + * {@code InstalledCode.invalidate(boolean deoptimize, int invalidationReason)} so that this * code can be compiled against older JVMCI API. */ - @SuppressWarnings("unused") private static final Method invalidateInstalledCode; + @SuppressWarnings("unused") private static final Method invalidateInstalledCodeWithReasonMethodRef; + @SuppressWarnings("unused") private static final Method invalidateInstalledCodeWithoutReasonMethodRef; + + /** + * Reflective reference to {@code HotSpotNmethod.getInvalidationReason()} and + * {@code HotSpotNmethod.getInvalidationReasonDescription()} so that this code can be compiled + * against older JVMCI API. + */ + @SuppressWarnings("unused") private static final Method getInvalidationReasonMethodRef; + @SuppressWarnings("unused") private static final Method getInvalidationReasonDescriptionMethodRef; static { - Method method = null; - try { - method = HotSpotNmethod.class.getDeclaredMethod("setSpeculationLog", HotSpotSpeculationLog.class); - } catch (NoSuchMethodException e) { - } - setSpeculationLog = method; - method = null; + setSpeculationLog = findMethod(HotSpotNmethod.class, "setSpeculationLog", true, HotSpotSpeculationLog.class); + invalidateInstalledCodeWithReasonMethodRef = findMethod(HotSpotNmethod.class, "invalidate", false, boolean.class, int.class); + invalidateInstalledCodeWithoutReasonMethodRef = findMethod(InstalledCode.class, "invalidate", true, boolean.class); + getInvalidationReasonMethodRef = findMethod(HotSpotNmethod.class, "getInvalidationReason", false); + getInvalidationReasonDescriptionMethodRef = findMethod(HotSpotNmethod.class, "getInvalidationReasonDescription", false); + } + + private static Method findMethod(Class clazz, String methodName, boolean required, Class... args) { try { - method = InstalledCode.class.getDeclaredMethod("invalidate", boolean.class); + return clazz.getMethod(methodName, args); } catch (NoSuchMethodException e) { + if (required) { + throw new InternalError(e); + } + return null; } - invalidateInstalledCode = method; } /** @@ -127,9 +142,13 @@ public void setInstalledCode(InstalledCode code) { return; } - if (oldCode != INVALID_CODE && invalidateInstalledCode != null) { + if (oldCode != INVALID_CODE) { try { - invalidateInstalledCode.invoke(oldCode, false); + if (invalidateInstalledCodeWithReasonMethodRef != null) { + invalidateInstalledCodeWithReasonMethodRef.invoke(oldCode, false, ((HotSpotTruffleRuntime) runtime()).getJVMCIReplacedMethodInvalidationReason()); + } else if (invalidateInstalledCodeWithoutReasonMethodRef != null) { + invalidateInstalledCodeWithoutReasonMethodRef.invoke(oldCode, false); + } } catch (Error e) { throw e; } catch (Throwable throwable) { @@ -195,4 +214,56 @@ public SpeculationLog getCompilationSpeculationLog() { return HotSpotTruffleRuntimeServices.getCompilationSpeculationLog(this); } + @Override + protected void notifyDeoptimized(VirtualFrame frame) { + String reason = null; + try { + // This method may be called with {@code installedCode == + // INVALID_CODE} when the call target is not a root compilation and + // the parent compilation was deoptimized. + if (this.installedCode != INVALID_CODE && getInvalidationReasonDescriptionMethodRef != null) { + reason = (String) getInvalidationReasonDescriptionMethodRef.invoke(this.installedCode); + } + } catch (Exception e) { + throw new InternalError(e); + } finally { + runtime().getListener().onCompilationDeoptimized(this, frame, reason); + } + } + + private int getInvalidationReason() { + try { + if (getInvalidationReasonMethodRef != null) { + assert installedCode != INVALID_CODE : "Cannot get invalidation reason of INVALID_CODE"; + return (int) getInvalidationReasonMethodRef.invoke(this.installedCode); + } + } catch (Exception e) { + throw new InternalError(e); + } + return 0; + } + + /** + * When running as part of HotSpot we should pay special attention to CallTargets (CTs) that + * have been flushed from the code cache because they were cold (According to Code Cache's + * heuristics). Truffle's CallTargets' Profile counter don't decay. For that reason, we need + * special handling for cold (according to code cache heuristics) CTs that were flushed from the + * code cache. Otherwise, we can enter a recompilation cycle because Truffle will always see the + * method as hot (because the profile counters never reset). To handle this case we reset the CT + * profile whenever its prior compilation was invalidated because it was cold. + */ + @Override + public boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) { + if (!super.prepareForCompilation(rootCompilation, compilationTier, lastTier)) { + return false; + } + + if (installedCode != INVALID_CODE && getInvalidationReason() == ((HotSpotTruffleRuntime) runtime()).getColdMethodInvalidationReason()) { + resetCompilationProfile(); + installedCode = INVALID_CODE; + runtime().getListener().onProfileReset(this); + return false; + } + return true; + } } diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/hotspot/HotSpotTruffleRuntime.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/hotspot/HotSpotTruffleRuntime.java index 4af757aa6866..875f124deef9 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/hotspot/HotSpotTruffleRuntime.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/hotspot/HotSpotTruffleRuntime.java @@ -186,11 +186,24 @@ private Lazy lazy() { private final HotSpotVMConfigAccess vmConfigAccess; + /** + * This constant is used to detect when a method was invalidated by HotSpot because the code + * cache heuristic considered it cold. + */ + private final int coldMethodInvalidationReason; + + /** + * This constant is used when Truffle invalidates an installed code. + */ + private final int jvmciReplacedMethodInvalidationReason; + public HotSpotTruffleRuntime(TruffleCompilationSupport compilationSupport) { super(compilationSupport, Arrays.asList(HotSpotOptimizedCallTarget.class, InstalledCode.class, HotSpotThreadLocalHandshake.class, HotSpotTruffleRuntime.class)); installCallBoundaryMethods(null); this.vmConfigAccess = new HotSpotVMConfigAccess(HotSpotJVMCIRuntime.runtime().getConfigStore()); + this.jvmciReplacedMethodInvalidationReason = vmConfigAccess.getConstant("nmethod::InvalidationReason::JVMCI_REPLACED_WITH_NEW_CODE", Integer.class, -1); + this.coldMethodInvalidationReason = vmConfigAccess.getConstant("nmethod::InvalidationReason::UNLOADING_COLD", Integer.class, -1); int jvmciReservedReference0Offset = vmConfigAccess.getFieldOffset("JavaThread::_jvmci_reserved_oop0", Integer.class, "oop", -1); if (jvmciReservedReference0Offset == -1) { @@ -656,6 +669,14 @@ protected int getObjectAlignment() { return getVMOptionValue("ObjectAlignmentInBytes", Integer.class); } + public int getJVMCIReplacedMethodInvalidationReason() { + return this.jvmciReplacedMethodInvalidationReason; + } + + public int getColdMethodInvalidationReason() { + return this.coldMethodInvalidationReason; + } + @Override protected int getArrayIndexScale(Class componentType) { MetaAccessProvider meta = getMetaAccess();