diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index ce84758c0422..a99f0389f84b 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -2,6 +2,9 @@ This changelog summarizes major changes between GraalVM SDK versions. The main focus is on APIs exported by GraalVM SDK. +## Version 25.0.0 +* GR-60636 Truffle now stops compiling when the code cache fills up on HotSpot. A warning is printed when that happens. + ## Version 24.2.0 * GR-54905 When using Truffle NFI with the Panama backend, native access must now be granted to the Truffle module instead of the NFI Panama module. Use the `--enable-native-access=org.graalvm.truffle` Java command line option to enable the native access for the NFI Panama backend. * GR-57681 Added the ability to use `Value#as(byte[].class)` to copy the contents of a guest language byte buffer (`Value#hasBufferElements()`) to a new byte array. The new functionality has precedence over accessing the guest object as array (`Value#hasArrayElements()`) if both ways are available. diff --git a/truffle/CHANGELOG.md b/truffle/CHANGELOG.md index 5bdf5a7316d5..726947cfa804 100644 --- a/truffle/CHANGELOG.md +++ b/truffle/CHANGELOG.md @@ -2,6 +2,9 @@ This changelog summarizes major changes between Truffle versions relevant to languages implementors building upon the Truffle framework. The main focus is on APIs exported by Truffle. +## Version 24.2.0 +* GR-60636 Truffle now stops compiling when the code cache fills up on HotSpot. A warning is printed when that happens. + ## Version 24.2.0 * GR-57658 Added `TruffleLanguage.Env.getLanguageInfo(Class)` to lookup a `LanguageInfo` instance for a language class returned by `InteropLibrary.getLanguage(Object)`. * GR-57164 Added support for reading unaligned ints, shorts and long to `ByteArraySupport`. diff --git a/truffle/docs/Options.md b/truffle/docs/Options.md index 8146af85f55f..11e53ec6a390 100644 --- a/truffle/docs/Options.md +++ b/truffle/docs/Options.md @@ -81,6 +81,7 @@ The accepted values are: - `--engine.PartialBlockMaximumSize=[1, inf)` : Sets the maximum non-trivial Truffle node size for partial compilation of BlockNode nodes (default: 10000). - `--engine.SingleTierCompilationThreshold=[1, inf)` : Minimum number of invocations or loop iterations needed to compile a guest language root when not using multi tier (default: 1000). - `--engine.Splitting=true|false` : Enable automatic duplication of compilation profiles (splitting) (default: true). +- `--engine.StoppedCompilationRetryDelay=` : 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: 5000 - `--engine.TraceCompilation` : Print information for compilation results. - `--compiler.EncodedGraphCache` : Cache encoded graphs across Truffle compilations to speed up partial evaluation. (default: true). - `--compiler.FirstTierUseEconomy` : Whether to use the economy configuration in the first-tier compilations. (default: true, syntax: true|false) 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..3c4f55021354 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -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..05ac69f120a0 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -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..09f0fa9d373f 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; @@ -85,6 +86,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.logging.Level; @@ -148,6 +150,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 +308,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); @@ -492,6 +496,16 @@ public TruffleLogger getLogger(String loggerId) { return polyglotEngine != null ? loggerFactory.apply(loggerId) : null; } + private final AtomicBoolean logShutdownCompilations = new AtomicBoolean(true); + + /** + * Only log compilation shutdowns (see {@code OptimizedCallTarget.isCompilationStopped()}) once + * per engine. + */ + public AtomicBoolean logShutdownCompilations() { + return logShutdownCompilations; + } + @SuppressWarnings("static-method") public void mergeLoadedSources(Source[] sources) { OptimizedRuntimeAccessor.SOURCE.mergeLoadedSources(sources); 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..6f4cdaa7a1e3 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; @@ -81,6 +84,7 @@ import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.compiler.TruffleCompilable; import com.oracle.truffle.runtime.OptimizedRuntimeOptions.ExceptionAction; +import com.oracle.truffle.runtime.OptimizedTruffleRuntime.CompilationActivityMode; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.SpeculationLog; @@ -855,6 +859,58 @@ final boolean isCompilationFailed() { return compilationFailed; } + private CompilationActivityMode getCompilationActivityMode() { + CompilationActivityMode compilationActivityMode = runtime().getCompilationActivityMode(); + long stoppedTime = runtime().stoppedCompilationTime().get(); + if (compilationActivityMode == CompilationActivityMode.STOP_COMPILATION) { + if (stoppedTime != 0 && System.currentTimeMillis() - stoppedTime > engine.stoppedCompilationRetryDelay) { + runtime().stoppedCompilationTime().compareAndSet(stoppedTime, 0); + // Try again every StoppedCompilationRetryDelay milliseconds to potentially trigger + // a code cache sweep. + compilationActivityMode = CompilationActivityMode.RUN_COMPILATION; + } + } + + switch (compilationActivityMode) { + case RUN_COMPILATION: + // This is the common case - compilations are not stopped. + return CompilationActivityMode.RUN_COMPILATION; + case STOP_COMPILATION: + if (stoppedTime == 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 CompilationActivityMode.STOP_COMPILATION; + 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 && engine.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'."); + } + // Flush the compilation queue and mark all methods as not compilable. + for (OptimizedCallTarget target : runtime().getCompileQueue().getQueuedTargets(null)) { + target.cancelCompilation("Compilation permanently disabled due to full code cache."); + target.compilationFailed = true; + } + return CompilationActivityMode.SHUTDOWN_COMPILATION; + default: + throw CompilerDirectives.shouldNotReachHere("Invalid compilation activity mode: " + compilationActivityMode); + } + } + /** * 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 +922,7 @@ public final boolean compile(boolean lastTierCompilation) { if (!needsCompile(lastTier)) { return true; } + if (!isSubmittedForCompilation()) { if (!acceptForCompilation()) { // do not try to compile again @@ -873,6 +930,15 @@ public final boolean compile(boolean lastTierCompilation) { return false; } + CompilationActivityMode cam = getCompilationActivityMode(); + if (cam != CompilationActivityMode.RUN_COMPILATION) { + if (cam == CompilationActivityMode.SHUTDOWN_COMPILATION) { + // Compilation was shut down permanently. + compilationFailed = true; + } + return false; + } + CompilationTask task = null; // Do not try to compile this target concurrently, // but do not block other threads if compilation is not asynchronous. 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..6ea147cb42ca 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -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: 5000", // + usageSyntax = "", category = OptionCategory.EXPERT) // + public static final OptionKey StoppedCompilationRetryDelay = new OptionKey<>(5000L); + @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..6e492adefa6f 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -63,6 +63,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.logging.Level; @@ -912,6 +913,12 @@ private void notifyCompilationFailure(OptimizedCallTarget callTarget, Throwable protected void onEngineCreated(EngineData engine) { } + private final AtomicLong stoppedCompilationTime = new AtomicLong(0); + + public final AtomicLong stoppedCompilationTime() { + return stoppedCompilationTime; + } + @SuppressWarnings("try") public CompilationTask submitForCompilation(OptimizedCallTarget optimizedCallTarget, boolean lastTierCompilation) { Priority priority = new Priority(optimizedCallTarget.getCallAndLoopCount(), lastTierCompilation ? Priority.Tier.LAST : Priority.Tier.FIRST); @@ -1483,4 +1490,25 @@ static OptionCategory matchCategory(TruffleCompilerOptionDescriptor d) { } } + public enum CompilationActivityMode { + /** + * Process compilations regularly. + */ + RUN_COMPILATION, + /** + * Stop compilations temporarily. + */ + STOP_COMPILATION, + /** + * Shutdown compilations permanently. + */ + SHUTDOWN_COMPILATION; + } + + /** + * Returns the current host compilation activity mode. The default is to run compilations. + */ + protected 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..0e50c0ca9a31 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -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,45 @@ public boolean isLibGraalCompilationEnabled() { return compilationSupport instanceof LibGraalTruffleCompilationSupport; } + private 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 + protected CompilationActivityMode getCompilationActivityMode() { + int activityMode = 1; // Default is to run compilations + if (getCompilationActivityMode != null) { + try { + activityMode = (int) getCompilationActivityMode.invokeExact(HotSpotJVMCIRuntime.runtime()); + } catch (Throwable t) { + throw CompilerDirectives.shouldNotReachHere("Can't get HotSpot's compilation activity mode", t); + } + } + return resolveHotSpotActivityMode(activityMode); + } + + /** + * 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}. + */ + private static CompilationActivityMode resolveHotSpotActivityMode(int i) { + return switch (i) { + case 0 -> CompilationActivityMode.STOP_COMPILATION; + case 1 -> CompilationActivityMode.RUN_COMPILATION; + case 2 -> CompilationActivityMode.SHUTDOWN_COMPILATION; + default -> throw CompilerDirectives.shouldNotReachHere("Invalid CompilationActivityMode " + i); + }; + } }