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/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/MaximumCompilationsTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/MaximumCompilationsTest.java index 23e2a97150f3..8d2f05af4857 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/MaximumCompilationsTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/MaximumCompilationsTest.java @@ -24,22 +24,26 @@ */ package jdk.graal.compiler.truffle.test; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -import org.graalvm.polyglot.Context; -import org.junit.Assume; -import org.junit.Test; +import static org.junit.Assert.assertEquals; import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.compiler.TruffleCompilerListener; +import com.oracle.truffle.runtime.AbstractCompilationTask; import com.oracle.truffle.runtime.OptimizedCallTarget; import com.oracle.truffle.runtime.OptimizedTruffleRuntime; import com.oracle.truffle.runtime.OptimizedTruffleRuntimeListener; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Engine; +import org.junit.Assume; +import org.junit.Test; public class MaximumCompilationsTest { public static class AllwaysDeoptRoot extends RootNode { @@ -90,4 +94,125 @@ public void onCompilationFailed(OptimizedCallTarget target, String reason, boole } } } + + @Test + public void testUnlimitedRecompilations() { + Assume.assumeTrue(Truffle.getRuntime() instanceof OptimizedTruffleRuntime); + OptimizedTruffleRuntime optimizedTruffleRuntime = (OptimizedTruffleRuntime) Truffle.getRuntime(); + AtomicBoolean compilationResult = new AtomicBoolean(); + + try (Engine eng = Engine.newBuilder().allowExperimentalOptions(true).// + option("engine.BackgroundCompilation", "false").// + option("engine.CompileImmediately", "true").build()) { + try (Context ctx = Context.newBuilder().engine(eng).build()) { + ctx.enter(); + CallTarget callTarget = new AllwaysDeoptRoot().getCallTarget(); + optimizedTruffleRuntime.addListener(new CustomListener(callTarget, compilationResult)); + + for (int i = 0; i < 16; i++) { + callTarget.call(); + assertEquals(true, compilationResult.get()); + } + } + } + } + + @Test + public void testMaxTwoCompilations() throws InterruptedException { + Assume.assumeTrue(Truffle.getRuntime() instanceof OptimizedTruffleRuntime); + OptimizedTruffleRuntime optimizedTruffleRuntime = (OptimizedTruffleRuntime) Truffle.getRuntime(); + AtomicBoolean compilationResult = new AtomicBoolean(); + + try (Engine eng = Engine.newBuilder().allowExperimentalOptions(true).// + option("engine.BackgroundCompilation", "false").// + option("engine.CompileImmediately", "true").// + option("engine.MaximumCompilations", "2").build()) { + try (Context ctx = Context.newBuilder().engine(eng).build()) { + ctx.enter(); + + CallTarget callTarget = new AllwaysDeoptRoot().getCallTarget(); + optimizedTruffleRuntime.addListener(new CustomListener(callTarget, compilationResult)); + + callTarget.call(); + assertEquals(true, compilationResult.get()); + + callTarget.call(); + assertEquals(true, compilationResult.get()); + + callTarget.call(); + assertEquals(false, compilationResult.get()); + + TimeUnit.SECONDS.sleep(90); + + // The method will not be recompiled because it has reached all compilations + // possible in its lifetime + callTarget.call(); + assertEquals(false, compilationResult.get()); + } + } + } + + @Test + public void testMaxTwoCompilationsPerMinute() throws InterruptedException { + Assume.assumeTrue(Truffle.getRuntime() instanceof OptimizedTruffleRuntime); + OptimizedTruffleRuntime optimizedTruffleRuntime = (OptimizedTruffleRuntime) Truffle.getRuntime(); + AtomicBoolean compilationResult = new AtomicBoolean(); + + try (Engine eng = Engine.newBuilder().allowExperimentalOptions(true).// + option("engine.BackgroundCompilation", "false").// + option("engine.CompileImmediately", "true").// + option("engine.MaximumCompilations", "2").// + option("engine.MaximumCompilationsWindow", "1").build()) { + try (Context ctx = Context.newBuilder().engine(eng).build()) { + ctx.enter(); + + CallTarget callTarget = new AllwaysDeoptRoot().getCallTarget(); + optimizedTruffleRuntime.addListener(new CustomListener(callTarget, compilationResult)); + + callTarget.call(); + assertEquals(true, compilationResult.get()); + + callTarget.call(); + assertEquals(true, compilationResult.get()); + + // This shouldn't trigger the compilation because there was already two compilations + // of this call target in the last minute. + callTarget.call(); + assertEquals(false, compilationResult.get()); + + // Wait to make sure we overflow the compilation period + TimeUnit.SECONDS.sleep(90); + + // this should trigger a new compilation as there was no compilation of this call + // target in the last minute + callTarget.call(); + assertEquals(true, compilationResult.get()); + } + } + } + + private class CustomListener implements OptimizedTruffleRuntimeListener { + public CallTarget callTargetFilter = null; + public AtomicBoolean compilationResult = new AtomicBoolean(); + + CustomListener(CallTarget callTarget, AtomicBoolean compilationResult) { + this.callTargetFilter = callTarget; + this.compilationResult = compilationResult; + } + + @Override + public void onCompilationSuccess(OptimizedCallTarget target, AbstractCompilationTask task, TruffleCompilerListener.GraphInfo graph, TruffleCompilerListener.CompilationResultInfo result) { + OptimizedTruffleRuntimeListener.super.onCompilationSuccess(target, task, graph, result); + if (target == callTargetFilter) { + compilationResult.set(true); + } + } + + @Override + public void onCompilationFailed(OptimizedCallTarget target, String reason, boolean bailout, boolean permanentBailout, int tier, Supplier lazyStackTrace) { + if (target == callTargetFilter) { + compilationResult.set(false); + } + } + } } diff --git a/truffle/docs/Options.md b/truffle/docs/Options.md index f16a7c574075..bb1d2f1ea4f5 100644 --- a/truffle/docs/Options.md +++ b/truffle/docs/Options.md @@ -74,7 +74,8 @@ The accepted values are: - `--engine.FirstTierCompilationThreshold=[1, inf)` : Number of invocations or loop iterations needed to compile a guest language root in first tier under normal compilation load.Might be reduced/increased when compilation load is low/high if DynamicCompilationThresholds is enabled. (default: 400). - `--engine.FirstTierMinInvokeThreshold=[1, inf)` : Minimum number of calls before a call target is compiled in the first tier (default: 1) - `--engine.LastTierCompilationThreshold=[1, inf)` : Number of invocations or loop iterations needed to compile a guest language root in first tier under normal compilation load.Might be reduced/increased when compilation load is low/high if DynamicCompilationThresholds is enabled. (default: 10000). -- `--engine.MaximumCompilations=(-inf, inf)` : Maximum number of successful compilations for a single call target before a permanent bailout. Exceeding the limit will result in a compilation failure with the appropriate reason and there will be no further attempts to compile the call target. (negative integer means no limit, default: 100) +- `--engine.MaximumCompilations=(-inf, inf)` : Maximum number of successful compilations for a single call target before a temporary/permanent bailout. Exceeding this limit will result in a compilation failure with the appropriate reason and there will be no further attempts to compile the call target within the time window specified in MaximumCompilationsWindow. (negative integer means no limit, default: 100) +- `--engine.MaximumCompilationsWindow=(-inf, inf)` : Time window in minutes used to limit the number of compilations of a call target. If the value is a negative integer it means an infinite window. This parameter is ignored if MaximumCompilations is a negative integer. (default: -1) - `--engine.MinInvokeThreshold=[1, inf)` : Minimum number of calls before a call target is compiled (default: 3). - `--engine.Mode=latency|throughput` : Configures the execution mode of the engine. Available modes are 'latency' and 'throughput'. The default value balances between the two. - `--engine.MultiTier=true|false` : Whether to use multiple Truffle compilation tiers by default. (default: 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/EngineData.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineData.java index ad1d735268b5..e0ed8b5baade 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineData.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineData.java @@ -53,6 +53,7 @@ import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.FirstTierMinInvokeThreshold; import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.LastTierCompilationThreshold; import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.MaximumCompilations; +import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.MaximumCompilationsWindow; import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.MinInvokeThreshold; import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.Mode; import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.MultiTier; @@ -165,6 +166,7 @@ public final class EngineData { @CompilationFinal public boolean propagateCallAndLoopCount; @CompilationFinal public int propagateCallAndLoopCountMaxDepth; @CompilationFinal public int maximumCompilations; + @CompilationFinal public int maximumCompilationsWindowInMinutes; // computed fields. @CompilationFinal public int callThresholdInInterpreter; @@ -323,6 +325,7 @@ private void loadOptions(OptionValues options, SandboxPolicy sandboxPolicy) { // See usage of traversingFirstTierBonus for explanation of this formula. traversingFirstTierBonus = options.get(TraversingQueueFirstTierBonus) * options.get(LastTierCompilationThreshold) / options.get(FirstTierCompilationThreshold); maximumCompilations = options.get(MaximumCompilations); + maximumCompilationsWindowInMinutes = options.get(MaximumCompilationsWindow); traversingInvalidatedBonus = options.get(TraversingQueueInvalidatedBonus); traversingOSRBonus = options.get(TraversingQueueOSRBonus); 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 5ce4cc521c43..0b772bdacda8 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 @@ -42,6 +42,8 @@ import java.lang.ref.Reference; import java.lang.ref.WeakReference; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; @@ -300,6 +302,7 @@ public Class getType() { */ private volatile CompilationTask compilationTask; private int successfulCompilationsCount; + private Instant timeOfFirstCompilationInWindow; private volatile boolean needsSplit; @@ -537,6 +540,7 @@ public final RootNode getRootNode() { public final void resetCompilationProfile() { this.callCount = 0; this.callAndLoopCount = 0; + this.timeOfFirstCompilationInWindow = Instant.now(); } @Override @@ -842,8 +846,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() { @@ -1001,13 +1005,7 @@ public final boolean compile(boolean lastTierCompilation) { try { assert compilationTask == null; - if (engine.maximumCompilations >= 0 && successfulCompilationsCount >= engine.maximumCompilations) { - compilationFailed = true; - runtime().getListener().onCompilationStarted(this, new PresubmitFailureCompilationTask(engine.firstTierOnly, lastTier)); - String failureReason = String.format("Maximum compilation count %d reached.", engine.maximumCompilations); - runtime().getListener().onCompilationFailed(this, failureReason, true, true, - lastTier ? 2 : 1, null); - handleCompilationFailure(() -> failureReason, false, true, true); + if (blockNewCompilations(lastTier)) { return false; } this.compilationTask = task = runtime().submitForCompilation(this, lastTier); @@ -1024,6 +1022,58 @@ public final boolean compile(boolean lastTierCompilation) { return false; } + private boolean blockNewCompilations(boolean lastTier) { + // No limit on number of re-compilations + if (engine.maximumCompilations < 0) { + return false; + } + + // If there is a window specified for the maximum number of compilations we check if we + // should reset the number of compilations because we overflowed the window period. + if (engine.maximumCompilationsWindowInMinutes > 0) { + long ageInMinutes = ChronoUnit.MINUTES.between(timeOfFirstCompilationInWindow, Instant.now()); + if (ageInMinutes >= engine.maximumCompilationsWindowInMinutes) { + // This compilation would have been blocked if the window hadn't overflowed, + // therefore we print a log saying that compilations are now "enabled". I don't want + // to print this message if the number of compilations of the method hadn't reached + // the limit. + if (successfulCompilationsCount == engine.maximumCompilations) { + runtime().getListener().onCompilationReenabled(this); + } + + // Reset window information + successfulCompilationsCount = 0; + timeOfFirstCompilationInWindow = Instant.now(); + } + } + + if (successfulCompilationsCount >= engine.maximumCompilations) { + if (successfulCompilationsCount == engine.maximumCompilations) { + // This bailout will be permanent if there is no window set for the maximum number + // of compilations + compilationFailed = (engine.maximumCompilationsWindowInMinutes < 0); + String failureReason = String.format("Maximum compilation count %d reached.", engine.maximumCompilations); + + // If the bailout is not permanent, i.e., this method is blocked for new + // compilations only for a period, then I bump the {@code + // successfulCompilationsCount} + // counter by one to indicate that the method is temporarily blocked for new + // compilations. Since there is a time window set, this value will eventually be + // reset to zero, so its temporary value should be imaterial. + if (!compilationFailed) { + successfulCompilationsCount++; + } + + runtime().getListener().onCompilationStarted(this, new PresubmitFailureCompilationTask(engine.firstTierOnly, lastTier)); + runtime().getListener().onCompilationFailed(this, failureReason, true, compilationFailed, lastTier ? 2 : 1, null); + handleCompilationFailure(() -> failureReason, false, true, compilationFailed); + } + return true; + } + + return false; + } + public final boolean maybeWaitForTask(CompilationTask task) { boolean mayBeAsynchronous = engine.backgroundCompilation; runtime().finishCompilation(this, task, mayBeAsynchronous); @@ -1169,6 +1219,9 @@ public final boolean computeBlockCompilations() { @Override public void onCompilationSuccess(int compilationTier, boolean lastTier) { + if (this.timeOfFirstCompilationInWindow == null) { + timeOfFirstCompilationInWindow = Instant.now(); + } successfulCompilationsCount++; } diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeOptions.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeOptions.java index 82224e6cda72..daa941cf44ef 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeOptions.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeOptions.java @@ -205,11 +205,19 @@ public ExceptionAction apply(String s) { usageSyntax = "[1, inf)", category = OptionCategory.EXPERT) // public static final OptionKey LastTierCompilationThreshold = new OptionKey<>(10000); - @Option(help = "Maximum number of successful compilations for a single call target before a permanent bailout. Exceeding the limit will result in a compilation failure with the appropriate reason and " + // - "there will be no further attempts to compile the call target. (negative integer means no limit, default: 100)", // + @Option(help = "Maximum number of successful compilations for a single call target before a temporary/permanent bailout." + + "Exceeding this limit will result in a compilation failure with the appropriate reason and there will be " + // + "no further attempts to compile the call target within the time window specified in MaximumCompilationsWindow." + + "(negative integer means no limit, default: 100)", // usageSyntax = "(-inf, inf)", category = OptionCategory.EXPERT) // public static final OptionKey MaximumCompilations = new OptionKey<>(100); + @Option(help = "Time window in minutes used to limit the number of compilations of a call target." + + "If the value is a negative integer it means an infinite window." + // + "This parameter is ignored if MaximumCompilations is a negative integer. (default: -1)", // + usageSyntax = "(-inf, inf)", category = OptionCategory.EXPERT) // + public static final OptionKey MaximumCompilationsWindow = new OptionKey<>(-1); + @Option(help = "Minimum number of calls before a call target is compiled (default: 3).", usageSyntax = "[1, inf)", category = OptionCategory.EXPERT) // public static final OptionKey MinInvokeThreshold = new OptionKey<>(3); 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..5766d7a0f485 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 @@ -294,8 +294,25 @@ default void onCompilationInvalidated(OptimizedCallTarget target, Object source, * * @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) { + 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) { + } + + /** + * Notifies this object when compilations of {@code target} are enabled. + * + * @param target the call target whose compilations are enabled. + */ + default void onCompilationReenabled(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..6de212cae83b 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,18 @@ 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 onCompilationReenabled(OptimizedCallTarget target) { + invokeListeners((l) -> l.onCompilationReenabled(target)); + } + + @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 e5e8223a1808..ed8cefd95279 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 @@ -118,7 +118,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 ea87cd2a3d79..3a6a70c5ebc4 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 @@ -102,7 +102,9 @@ public static void install(OptimizedTruffleRuntime runtime) { private static final String FAILED_FORMAT = "opt failed " + TARGET_FORMAT + "|" + TIER_FORMAT + "|Time %18s|Reason: %s|UTC %s|Src %s"; private static final String PADDING = " "; private static final String INV_FORMAT = "opt inval. " + TARGET_FORMAT + " " + PADDING + "|UTC %s|Src %s|Reason %s"; - private static final String DEOPT_FORMAT = "opt deopt " + TARGET_FORMAT + "|" + PADDING + "|UTC %s|Src %s"; + private static final String DEOPT_FORMAT = "opt deopt " + TARGET_FORMAT + "|" + PADDING + "|UTC %s|Src %s|Reason %s"; + private static final String REPROF_FORMAT = "opt reprofile " + TARGET_FORMAT + "|" + PADDING + "|UTC %s|Src %s"; + private static final String ENABLED_FORMAT = "opt enabled " + TARGET_FORMAT + "|UTC %s|Src %s"; // @formatter:on @Override @@ -222,9 +224,22 @@ 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(), + target.id, + safeTargetName(target), + 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), @@ -269,6 +284,17 @@ public void onCompilationSuccess(OptimizedCallTarget target, AbstractCompilation currentCompilation.remove(); } + public void onCompilationReenabled(OptimizedCallTarget target) { + if (target.engine.traceCompilationDetails) { + log(target, String.format(ENABLED_FORMAT, + target.engineId(), + target.id, + safeTargetName(target), + TIME_FORMATTER.format(ZonedDateTime.now()), + formatSourceSection(safeSourceSection(target)))); + } + } + static String getCompilationId(CompilationResultInfo result) { /* * When the CompilationResultInfo class is from GraalVM, the getCompilationId method might 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..2833332ab2ec 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,8 @@ import java.lang.reflect.Method; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +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 +99,35 @@ 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 = initialize(HotSpotNmethod.class, "setSpeculationLog", HotSpotSpeculationLog.class); + invalidateInstalledCodeWithReasonMethodRef = initialize(HotSpotNmethod.class, "invalidate", boolean.class, int.class); + invalidateInstalledCodeWithoutReasonMethodRef = initialize(InstalledCode.class, "invalidate", boolean.class); + getInvalidationReasonMethodRef = initialize(HotSpotNmethod.class, "getInvalidationReason"); + getInvalidationReasonDescriptionMethodRef = initialize(HotSpotNmethod.class, "getInvalidationReasonDescription"); + } + + private static Method initialize(Class clx, String methodName, Class... args) { try { - method = InstalledCode.class.getDeclaredMethod("invalidate", boolean.class); + return clx.getDeclaredMethod(methodName, args); } catch (NoSuchMethodException e) { + return null; } - invalidateInstalledCode = method; } /** @@ -122,20 +135,11 @@ public HotSpotOptimizedCallTarget(EngineData engine) { */ public void setInstalledCode(InstalledCode code) { assert code != null : "code must never become null"; - InstalledCode oldCode = this.installedCode; - if (oldCode == code) { + if (this.installedCode == code) { return; } - if (oldCode != INVALID_CODE && invalidateInstalledCode != null) { - try { - invalidateInstalledCode.invoke(oldCode, false); - } catch (Error e) { - throw e; - } catch (Throwable throwable) { - throw new InternalError(throwable); - } - } + invalidateExistingCode(); // A default nmethod can be called from entry points in the VM (e.g., Method::_code) // and so allowing it to be installed here would invalidate the truth of @@ -174,9 +178,23 @@ private void tetherSpeculationLog(HotSpotNmethod nmethod) throws Error, Internal } } + /** + * This method will reset the execution profile counters of this call target if the installed + * code was invalidated because it became cold. + * + * @return whether the currently installed code is valid/executable. + */ @Override public boolean isValid() { - return installedCode.isValid(); + boolean isValid = installedCode.isValid(); + if (!isValid && installedCode != INVALID_CODE) { + if (getInvalidationReason() == ((HotSpotTruffleRuntime) runtime()).getColdMethodInvalidationReason()) { + invalidateExistingCode(); + resetCompilationProfile(); + runtime().getListener().onProfileReset(this); + } + } + return isValid; } @Override @@ -195,4 +213,51 @@ public SpeculationLog getCompilationSpeculationLog() { return HotSpotTruffleRuntimeServices.getCompilationSpeculationLog(this); } + @Override + protected void notifyDeoptimized(VirtualFrame frame) { + runtime().getListener().onCompilationDeoptimized(this, frame, getInvalidationReasonDescription()); + } + + @TruffleBoundary + private void invalidateExistingCode() throws Error, InternalError { + if (this.installedCode != INVALID_CODE) { + try { + InstalledCode oldCode = this.installedCode; + if (invalidateInstalledCodeWithReasonMethodRef != null) { + invalidateInstalledCodeWithReasonMethodRef.invoke(this.installedCode, false, ((HotSpotTruffleRuntime) runtime()).getJVMCIReplacedMethodInvalidationReason()); + } else if (invalidateInstalledCodeWithoutReasonMethodRef != null) { + invalidateInstalledCodeWithoutReasonMethodRef.invoke(oldCode, false); + } + this.installedCode = INVALID_CODE; + } catch (Error e) { + throw e; + } catch (Throwable throwable) { + throw new InternalError(throwable); + } + } + } + + @TruffleBoundary + private int getInvalidationReason() { + try { + if (getInvalidationReasonMethodRef != null) { + return (int) getInvalidationReasonMethodRef.invoke(this.installedCode); + } + } catch (Exception e) { + // ignore + } + return 0; + } + + @TruffleBoundary + private String getInvalidationReasonDescription() { + try { + if (getInvalidationReasonDescriptionMethodRef != null) { + return (String) getInvalidationReasonDescriptionMethodRef.invoke(this.installedCode); + } + } catch (Exception e) { + // ignore + } + return null; + } } 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 0e50c0ca9a31..08347dce1370 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 @@ -176,11 +176,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) { @@ -644,6 +657,14 @@ protected int getObjectAlignment() { return getVMOptionValue("ObjectAlignmentInBytes", Integer.class); } + public int getColdMethodInvalidationReason() { + return this.coldMethodInvalidationReason; + } + + public int getJVMCIReplacedMethodInvalidationReason() { + return this.jvmciReplacedMethodInvalidationReason; + } + @Override protected int getArrayIndexScale(Class componentType) { MetaAccessProvider meta = getMetaAccess();