diff --git a/truffle/src/com.oracle.truffle.compiler/src/com/oracle/truffle/compiler/TruffleCompilerRuntime.java b/truffle/src/com.oracle.truffle.compiler/src/com/oracle/truffle/compiler/TruffleCompilerRuntime.java index ba144087f576..75669f9ac3c7 100644 --- a/truffle/src/com.oracle.truffle.compiler/src/com/oracle/truffle/compiler/TruffleCompilerRuntime.java +++ b/truffle/src/com.oracle.truffle.compiler/src/com/oracle/truffle/compiler/TruffleCompilerRuntime.java @@ -358,5 +358,4 @@ default ResolvedJavaType resolveType(MetaAccessProvider metaAccess, String class * silent. */ boolean isSuppressedFailure(TruffleCompilable compilable, Supplier serializedException); - } diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/BackgroundCompileQueue.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/BackgroundCompileQueue.java index bd4009f37cfa..3dd4faa630b0 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/BackgroundCompileQueue.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/BackgroundCompileQueue.java @@ -218,7 +218,8 @@ public int getQueueSize() { /** * Return call targets waiting in queue. This does not include call targets currently being - * compiled. + * compiled. If {@code engine} is {@code null}, the call targets for all engines are returned, + * otherwise only the call targets belonging to {@code engine} will be returned. */ public Collection getQueuedTargets(EngineData engine) { BlockingQueue queue = this.compilationQueue; @@ -230,7 +231,7 @@ public Collection getQueuedTargets(EngineData engine) { CompilationTask.ExecutorServiceWrapper[] array = queue.toArray(new CompilationTask.ExecutorServiceWrapper[0]); for (CompilationTask.ExecutorServiceWrapper wrapper : array) { OptimizedCallTarget target = wrapper.compileTask.targetRef.get(); - if (target != null && target.engine == engine) { + if (target != null && (engine == null || target.engine == engine)) { queuedTargets.add(target); } } 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 250483505272..a0287a15f380 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 @@ -68,6 +68,7 @@ import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.SplittingMaxCalleeSize; import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.SplittingMaxPropagationDepth; import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.SplittingTraceEvents; +import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.StoppedCompilationRetryDelay; import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.TraceCompilation; import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.TraceCompilationDetails; import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.TraceDeoptimizeFrame; @@ -148,6 +149,7 @@ public final class EngineData { @CompilationFinal public boolean traceDeoptimizeFrame; @CompilationFinal public boolean compileAOTOnCreate; @CompilationFinal public boolean firstTierOnly; + @CompilationFinal public long stoppedCompilationRetryDelay; // compilation queue options @CompilationFinal public boolean priorityQueue; @@ -305,6 +307,7 @@ private void loadOptions(OptionValues options, SandboxPolicy sandboxPolicy) { this.firstTierOnly = options.get(Mode) == EngineModeEnum.LATENCY; this.propagateCallAndLoopCount = options.get(PropagateLoopCountToLexicalSingleCaller); this.propagateCallAndLoopCountMaxDepth = options.get(PropagateLoopCountToLexicalSingleCallerMaxDepth); + this.stoppedCompilationRetryDelay = options.get(StoppedCompilationRetryDelay); // compilation queue options priorityQueue = options.get(PriorityQueue); 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 028aafc0fabe..c448a7cf92c6 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 @@ -43,6 +43,7 @@ import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -51,7 +52,9 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Supplier; +import java.util.logging.Level; +import com.oracle.truffle.api.TruffleLogger; import org.graalvm.options.OptionKey; import org.graalvm.options.OptionValues; @@ -855,6 +858,60 @@ final boolean isCompilationFailed() { return compilationFailed; } + final boolean isCompilationStopped() { + OptimizedTruffleRuntime.CompilationActivityMode compilationActivityMode = runtime().getCompilationActivityMode(); + long sct = runtime().stoppedCompilationTime().get(); + if (compilationActivityMode == OptimizedTruffleRuntime.CompilationActivityMode.STOP_COMPILATION) { + if (sct != 0 && System.currentTimeMillis() - sct > engine.stoppedCompilationRetryDelay) { + runtime().stoppedCompilationTime().compareAndSet(sct, 0); + // Try again every StoppedCompilationRetryDelay milliseconds to potentially trigger a code cache sweep. + compilationActivityMode = OptimizedTruffleRuntime.CompilationActivityMode.RUN_COMPILATION; + } + } + + switch (compilationActivityMode) { + case RUN_COMPILATION : { + // This is the common case - compilations are not stopped. + return false; + } + case STOP_COMPILATION : { + if (sct == 0) { + runtime().stoppedCompilationTime().compareAndSet(0, System.currentTimeMillis()); + } + // Flush the compilations queue for now. There's still a chance that compilation + // will be re-enabled eventually, if the hosts code cache can be cleaned up. + Collection targets = runtime().getCompileQueue().getQueuedTargets(null); + // If there's just a single compilation target in the queue, the chance is high that it is + // the one we've just added after the StoppedCompilationRetryDelay ran out, so keep it to + // potentially trigger a code cache sweep. + if (targets.size() > 1) { + for (OptimizedCallTarget target : targets) { + target.cancelCompilation("Compilation temporary disabled due to full code cache."); + } + } + return true; + } + case SHUTDOWN_COMPILATION : { + // Compilation was shut down permanently because the hosts code cache ran full and + // the host was configured without support for code cache sweeping. + TruffleLogger logger = engine.getLogger("engine"); + // The logger can be null if the engine is closed. + if (logger != null && runtime().logShutdownCompilations().compareAndExchange(true, false)) { + logger.log(Level.WARNING, "Truffle compilations permanently disabled because of full code cache. " + + "Increase the code cache size using '-XX:ReservedCodeCacheSize=' and/or run with '-XX:+UseCodeCacheFlushing -XX:+MethodFlushing'."); + } + try { + runtime().getCompileQueue().shutdownAndAwaitTermination(100 /* milliseconds */); + } catch (RuntimeException re) { + // Best effort, ignore failure + } + return true; + } + default : CompilerDirectives.shouldNotReachHere("Invalid compilation activity mode: " + compilationActivityMode); + } + return false; + } + /** * Returns true if the call target was already compiled or was compiled * synchronously. Returns false if compilation was not scheduled or is happening in @@ -866,6 +923,7 @@ public final boolean compile(boolean lastTierCompilation) { if (!needsCompile(lastTier)) { return true; } + if (!isSubmittedForCompilation()) { if (!acceptForCompilation()) { // do not try to compile again @@ -881,6 +939,10 @@ public final boolean compile(boolean lastTierCompilation) { return true; } + if (isCompilationStopped()) { + return false; + } + ensureInitialized(); if (!isSubmittedForCompilation()) { if (lastTier) { 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 63cc586ba584..3ba95f646474 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 @@ -158,6 +158,15 @@ public ExceptionAction apply(String s) { // TODO: GR-29949 public static final OptionKey CompilerIdleDelay = new OptionKey<>(10000L); + @Option(help = "Before the Truffle runtime submits an OptimizedCallTarget for compilation, it checks for the " + + "compilation activity mode in the host VM. If the activity mode indicates a full code " + + "cache, no new compilation requests are submitted and the compilation queue is flushed. " + + "After 'StoppedCompilationRetryDelay' milliseconds new compilations will be submitted again " + + "(which might trigger a sweep of the code cache and a reset of the compilation activity mode in the host JVM)." + + "The option is only supported on the HotSpot Truffle runtime. On runtimes which don't support it the option has no effect. default: 1000", + usageSyntax = "", category = OptionCategory.EXPERT) + public static final OptionKey StoppedCompilationRetryDelay = new OptionKey<>(1000L); + @Option(help = "Manually set the number of compiler threads. By default, the number of compiler threads is scaled with the number of available cores on the CPU.", usageSyntax = "[1, inf)", category = OptionCategory.EXPERT, // stability = OptionStability.STABLE, sandbox = SandboxPolicy.UNTRUSTED) // public static final OptionKey CompilerThreads = new OptionKey<>(-1); diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntime.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntime.java index 41bf7d063756..3fc095573242 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntime.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedTruffleRuntime.java @@ -63,6 +63,8 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.logging.Level; @@ -912,6 +914,19 @@ private void notifyCompilationFailure(OptimizedCallTarget callTarget, Throwable protected void onEngineCreated(EngineData engine) { } + + private final AtomicLong stoppedCompilationTime = new AtomicLong(0); + + public final AtomicLong stoppedCompilationTime() { + return stoppedCompilationTime; + } + + private final AtomicBoolean logShutdownCompilations = new AtomicBoolean(true); + + public final AtomicBoolean logShutdownCompilations() { + return logShutdownCompilations; + } + @SuppressWarnings("try") public CompilationTask submitForCompilation(OptimizedCallTarget optimizedCallTarget, boolean lastTierCompilation) { Priority priority = new Priority(optimizedCallTarget.getCallAndLoopCount(), lastTierCompilation ? Priority.Tier.LAST : Priority.Tier.FIRST); @@ -1483,4 +1498,30 @@ static OptionCategory matchCategory(TruffleCompilerOptionDescriptor d) { } } + /** + * Represents HotSpot's compilation activity mode which is one of: + * {@code stop_compilation = 0}, {@code run_compilation = 1} or {@code shutdown_compilation = 2} + * Should be in sync with the {@code CompilerActivity} enum in {@code hotspot/share/compiler/compileBroker.hpp} + */ + public enum CompilationActivityMode { + STOP_COMPILATION, + RUN_COMPILATION, + SHUTDOWN_COMPILATION; + + static public CompilationActivityMode fromInteger(int i) { + return switch (i) { + case 0 -> STOP_COMPILATION; + case 1 -> RUN_COMPILATION; + case 2 -> SHUTDOWN_COMPILATION; + default -> throw new RuntimeException("Invalid CompilationActivityMode " + i); + }; + } + } + + /** + * Returns the current host compilation activity mode. The default is to run compilations. + */ + public CompilationActivityMode getCompilationActivityMode() { + return CompilationActivityMode.RUN_COMPILATION; + } } 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 c49fab962417..e143ceb63394 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 @@ -40,6 +40,9 @@ */ package com.oracle.truffle.runtime.hotspot; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.ref.Reference; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -692,4 +695,31 @@ public boolean isLibGraalCompilationEnabled() { return compilationSupport instanceof LibGraalTruffleCompilationSupport; } + static final MethodHandle getCompilationActivityMode; + static { + MethodHandle mHandle = null; + try { + MethodType mt = MethodType.methodType(int.class); + mHandle = MethodHandles.lookup().findVirtual(HotSpotJVMCIRuntime.class, "getCompilationActivityMode", mt); + } catch (NoSuchMethodException | IllegalAccessException e) { + // Older JVMCI runtimes might not support `getCompilationActivityMode()` + } + getCompilationActivityMode = mHandle; + } + + /** + * Returns the current host compilation activity mode based on HotSpot's code cache state. + */ + @Override + public CompilationActivityMode getCompilationActivityMode() { + int activityMode = 1; // Default is to run compilations + if (getCompilationActivityMode != null) { + try { + activityMode = (int) getCompilationActivityMode.invokeExact(HotSpotJVMCIRuntime.runtime()); + } catch (Throwable t) { + throw new RuntimeException("Can't get HotSpot's compilation activity mode", t); + } + } + return CompilationActivityMode.fromInteger(activityMode); + } }