diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 45f3eb8a4039..e4652cc4ea2a 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -17,6 +17,7 @@ This changelog summarizes major changes between GraalVM SDK versions. The main f * GR-40931 Using virtual threads in a native-image will now emulate virtual threads using platform threads until Loom support for Truffle languages in native-image is implemented. * GR-48481: It is now possible to depend on platform-specific Maven artifacts when using polyglot isolates. The `-isolate` Maven artifacts remain platform-independent. However, if platform specific builds are required, consider adding a platform specific suffix to the Maven artifact name, such as `-isolate-linux-amd64`. The reduced binary size of platform specific dependencies can improve build times and reduce the distribution size of your application. For more details, see [Embedding Languages](https://www.graalvm.org/latest/reference-manual/embed-languages/#polyglot-isolates). * GR-48481: The Python language is now available as a polyglot isolate. +* GR-54674: Added the options `engine.TraceSourceCache` and `engine.TraceSourceCacheDetails` to allow tracing of source cache hits, misses, failures and evictions. ## Version 24.0.0 * (GR-49334) Deprecated the `FileSystems#allowLanguageHomeAccess()` method and introduced `FileSystem#allowInternalResourceAccess()` as a replacement. To ensure compatibility, both methods now provide support for language homes and internal resources. diff --git a/truffle/docs/Options.md b/truffle/docs/Options.md index a9798291d3cd..8146af85f55f 100644 --- a/truffle/docs/Options.md +++ b/truffle/docs/Options.md @@ -52,6 +52,8 @@ They are useful to users and language and tool implementers. - `--engine.RelaxStaticObjectSafetyChecks` : On property accesses, the Static Object Model does not perform shape checks and uses unsafe casts - `--engine.SynchronousThreadLocalActionMaxWait=[0, inf)` : How long to wait for other threads to reach a synchronous ThreadLocalAction before cancelling it, in seconds. 0 means no limit. - `--engine.SynchronousThreadLocalActionPrintStackTraces` : Print thread stacktraces when a synchronous ThreadLocalAction is waiting for more than SynchronousThreadLocalActionMaxWait seconds. +- `--engine.TraceSourceCache` : Print information for source cache misses/evictions/failures. +- `--engine.TraceSourceCacheDetails` : Print information for all source cache events including hits and uncached misses. - `--engine.TraceStackTraceInterval=[1, inf)` : Prints the stack trace for all threads for a time interval. By default 0, which disables the output. - `--engine.DebugCacheCompileUseLastTier=true|false` : If true uses the last tier instead of the first tier compiler. By default the last tier compiler is used (default: true). - `--engine.BackgroundCompilation=true|false` : Enable asynchronous truffle compilation in background threads (default: true) diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/source/SourceCacheTest.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/source/SourceCacheTest.java new file mode 100644 index 000000000000..12c3344d3125 --- /dev/null +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/source/SourceCacheTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2024, 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.api.test.source; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.PolyglotException; +import org.graalvm.polyglot.Source; +import org.junit.Assert; +import org.junit.Test; +import org.junit.function.ThrowingRunnable; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.exception.AbstractTruffleException; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.api.test.GCUtils; +import com.oracle.truffle.api.test.common.TestUtils; +import com.oracle.truffle.tck.tests.TruffleTestAssumptions; + +public class SourceCacheTest { + + @Test + public void testTraceSourceCache() throws Throwable { + testCommon(SourceCacheTestLanguage.ID, Map.of("engine.TraceSourceCache", "true"), "[miss]", null); + } + + @Test + public void testTraceSourceCacheDetails() throws Throwable { + testCommon(SourceCacheTestLanguage.ID, Map.of("engine.TraceSourceCacheDetails", "true"), "[miss, hit]", null); + } + + @Test + public void testTraceSourceCacheFailure() throws Throwable { + testCommon(SourceCacheFailureTestLanguage.ID, Map.of("engine.TraceSourceCache", "true"), "[fail]", "DummyParseException"); + } + + @Test + public void testTraceSourceCacheEviction() throws IOException { + TruffleTestAssumptions.assumeWeakEncapsulation(); + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); Context context = Context.newBuilder().option("engine.TraceSourceCache", "true").out(out).err(out).build()) { + Source auxiliarySource = Source.newBuilder(SourceCacheTestLanguage.ID, "x", "AuxiliarySource").build(); + String sourceName = "TestSource"; + String[] sourceHash = new String[1]; + for (int i = 0; i < 2; i++) { + Source source = Source.newBuilder(SourceCacheTestLanguage.ID, "", sourceName).build(); + int sourceHashCode = context.eval(source).asInt(); + sourceHash[0] = String.format("0x%08x", sourceHashCode); + WeakReference souceRef = new WeakReference<>(source); + source = null; + GCUtils.assertGc("Source was not collected", souceRef); + context.eval(auxiliarySource); + } + List logs = new ArrayList<>(); + forEachLog(out.toByteArray(), (matcher) -> { + String logType = matcher.group(1); + if ("evict".equals(logType)) { + logs.add(logType); + Assert.assertEquals(sourceHash[0], matcher.group(2)); + Assert.assertEquals(sourceName, matcher.group(3)); + } + }); + // at least one + Assert.assertFalse(logs.isEmpty()); + Assert.assertEquals("evict", logs.get(1)); + } + } + + private static final Pattern LOG_PATTERN = Pattern.compile("^\\[engine] source-cache-(\\w+)\\s+(0x[0-9a-f]+) (\\w+).*UTC \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}((?:\\|Error .*)?)$"); + + private static void forEachLog(byte[] logBytes, Consumer logConsumer) throws IOException { + try (BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(logBytes)))) { + String line; + while ((line = br.readLine()) != null) { + Matcher matcher = LOG_PATTERN.matcher(line); + if (matcher.find()) { + logConsumer.accept(matcher); + } + } + } + } + + private static void testCommon(String languageId, Map options, String expectedLogs, String failMessage) throws Throwable { + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); Context context = Context.newBuilder().options(options).out(out).err(out).build()) { + String sourceName = "TestSource"; + Source source = Source.newBuilder(languageId, "", sourceName).build(); + String[] sourceHash = new String[1]; + ThrowingRunnable testRunnable = () -> { + int sourceHashCode = context.eval(source).asInt(); + sourceHash[0] = String.format("0x%08x", sourceHashCode); + }; + if (failMessage != null) { + Assert.assertThrows(PolyglotException.class, testRunnable); + } else { + testRunnable.run(); + context.eval(source); + } + List logs = new ArrayList<>(); + forEachLog(out.toByteArray(), (matcher) -> { + String logType = matcher.group(1); + logs.add(logType); + if (!"fail".equals(logType)) { + Assert.assertEquals(sourceHash[0], matcher.group(2)); + } else { + Assert.assertTrue(matcher.group().endsWith("Error " + failMessage)); + } + Assert.assertEquals(sourceName, matcher.group(3)); + }); + Assert.assertEquals(expectedLogs, Arrays.toString(logs.toArray())); + } + } + + @TruffleLanguage.Registration + static class SourceCacheTestLanguage extends TruffleLanguage { + static final String ID = TestUtils.getDefaultLanguageId(SourceCacheTestLanguage.class); + + @Override + protected Env createContext(Env env) { + return env; + } + + @Override + protected CallTarget parse(ParsingRequest request) throws Exception { + int sourceHashCode = request.getSource().hashCode(); + return new RootNode(this) { + @Override + public Object execute(VirtualFrame frame) { + return sourceHashCode; + } + }.getCallTarget(); + } + } + + @SuppressWarnings("serial") + static class ParseException extends AbstractTruffleException { + + ParseException() { + super("DummyParseException"); + } + } + + @TruffleLanguage.Registration + static class SourceCacheFailureTestLanguage extends TruffleLanguage { + static final String ID = TestUtils.getDefaultLanguageId(SourceCacheFailureTestLanguage.class); + + @Override + protected Env createContext(Env env) { + return env; + } + + @Override + protected CallTarget parse(ParsingRequest request) throws Exception { + throw new ParseException(); + } + } +} diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java index ab2ab7752283..ee0feb2a8581 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java @@ -784,6 +784,8 @@ public abstract Iterator mergeHostGuestFrames(Object polyglotEngine, S public abstract void setIsolatePolyglot(AbstractPolyglotImpl instance); public abstract Object getEngineData(Object polyglotEngine); + + public abstract long getEngineId(Object polyglotEngine); } public abstract static class LanguageSupport extends Support { @@ -1145,6 +1147,8 @@ protected RuntimeSupport(Object permission) { public abstract RootCallTarget newCallTarget(CallTarget source, RootNode rootNode); + public abstract long getCallTargetId(CallTarget target); + public abstract boolean isLoaded(CallTarget callTarget); public abstract void notifyOnLoad(CallTarget callTarget); diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultCallTarget.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultCallTarget.java index 62bd35a1ad8d..bf0636471c31 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultCallTarget.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultCallTarget.java @@ -42,6 +42,8 @@ import static com.oracle.truffle.api.impl.DefaultTruffleRuntime.getRuntime; +import java.util.concurrent.atomic.AtomicLong; + import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.TruffleSafepoint; import com.oracle.truffle.api.frame.VirtualFrame; @@ -61,9 +63,14 @@ public final class DefaultCallTarget implements RootCallTarget { private volatile boolean initialized; private volatile boolean loaded; + public final long id; + + private static final AtomicLong ID_COUNTER = new AtomicLong(0); + DefaultCallTarget(RootNode function) { this.rootNode = function; this.rootNode.adoptChildren(); + this.id = ID_COUNTER.incrementAndGet(); } @Override diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java index cf133532f96d..d69e9f3381d8 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java @@ -88,6 +88,15 @@ public RootCallTarget newCallTarget(CallTarget sourceCallTarget, RootNode rootNo return new DefaultCallTarget(rootNode); } + @Override + public long getCallTargetId(CallTarget target) { + if (target instanceof DefaultCallTarget) { + return ((DefaultCallTarget) target).id; + } else { + return 0; + } + } + @Override public boolean isLegacyCompilerOption(String key) { return false; diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java index abf39ed79294..868dcd2ffceb 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java @@ -74,7 +74,6 @@ import java.util.logging.Level; import java.util.logging.LogRecord; -import com.oracle.truffle.api.impl.JDKAccessor; import org.graalvm.collections.Pair; import org.graalvm.nativeimage.ImageInfo; import org.graalvm.options.OptionKey; @@ -107,6 +106,7 @@ import com.oracle.truffle.api.TruffleLogger; import com.oracle.truffle.api.TruffleSafepoint; import com.oracle.truffle.api.impl.Accessor; +import com.oracle.truffle.api.impl.JDKAccessor; import com.oracle.truffle.api.impl.TruffleLocator; import com.oracle.truffle.api.instrumentation.ContextsListener; import com.oracle.truffle.api.instrumentation.ProbeNode; @@ -2184,6 +2184,11 @@ public void setIsolatePolyglot(AbstractPolyglotImpl instance) { public Object getEngineData(Object polyglotEngine) { return ((PolyglotEngineImpl) polyglotEngine).runtimeData; } + + @Override + public long getEngineId(Object polyglotEngine) { + return ((PolyglotEngineImpl) polyglotEngine).engineId; + } } abstract static class AbstractClassLoaderSupplier implements Supplier { diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java index f88e2f346133..784d5e29450b 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java @@ -3527,6 +3527,10 @@ boolean patch(PolyglotContextConfig newConfig) { } finally { engine.leave(prev, this); } + PolyglotSharingLayer.Shared s = layer.shared; + if (s != null) { + s.sourceCache.patch(TracingSourceCacheListener.createOrNull(engine)); + } return true; } diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java index 9081456a11c1..d8157032533e 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java @@ -74,6 +74,7 @@ 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.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Predicate; @@ -144,6 +145,7 @@ final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotIm static final int HOST_LANGUAGE_INDEX = 0; static final String HOST_LANGUAGE_ID = "host"; + private static final AtomicLong ENGINE_COUNTER = new AtomicLong(); static final String ENGINE_ID = "engine"; static final String OPTION_GROUP_ENGINE = ENGINE_ID; static final String OPTION_GROUP_COMPILER = "compiler"; @@ -248,6 +250,8 @@ final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotIm final InternalResourceRoots internalResourceRoots; + final long engineId; + @SuppressWarnings("unchecked") PolyglotEngineImpl(PolyglotImpl impl, SandboxPolicy sandboxPolicy, String[] permittedLanguages, DispatchOutputStream out, DispatchOutputStream err, InputStream in, OptionValuesImpl engineOptions, @@ -256,6 +260,7 @@ final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotIm boolean allowExperimentalOptions, boolean boundEngine, boolean preInitialization, MessageTransport messageInterceptor, LogHandler logHandler, TruffleLanguage hostImpl, boolean hostLanguageOnly, AbstractPolyglotHostService polyglotHostService) { + this.engineId = ENGINE_COUNTER.incrementAndGet(); this.apiAccess = impl.getAPIAccess(); this.sandboxPolicy = sandboxPolicy; this.messageInterceptor = messageInterceptor; @@ -496,6 +501,7 @@ void notifyCreated() { @SuppressWarnings("unchecked") PolyglotEngineImpl(PolyglotEngineImpl prototype) { + this.engineId = ENGINE_COUNTER.incrementAndGet(); this.apiAccess = prototype.apiAccess; this.sandboxPolicy = prototype.sandboxPolicy; this.messageInterceptor = prototype.messageInterceptor; diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineOptions.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineOptions.java index 111c8f990cc2..896a3649fba8 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineOptions.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineOptions.java @@ -149,6 +149,12 @@ final class PolyglotEngineOptions { "Asserts that enter and return are always called in pairs on ProbeNode, verifies correct behavior of wrapper nodes. Java asserts need to be turned on for this option to have an effect. (default: false)")// static final OptionKey AssertProbes = new OptionKey<>(false); + @Option(category = OptionCategory.EXPERT, stability = OptionStability.STABLE, help = "Print information for source cache misses/evictions/failures.")// + static final OptionKey TraceSourceCache = new OptionKey<>(false); + + @Option(category = OptionCategory.EXPERT, stability = OptionStability.STABLE, help = "Print information for all source cache events including hits and uncached misses.")// + static final OptionKey TraceSourceCacheDetails = new OptionKey<>(false); + enum StaticObjectStorageStrategies { DEFAULT, ARRAY_BASED, diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSharingLayer.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSharingLayer.java index 00bb9c441827..2036ce68cd98 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSharingLayer.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSharingLayer.java @@ -47,6 +47,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; @@ -78,6 +79,8 @@ */ final class PolyglotSharingLayer { + private static final AtomicLong LAYER_COUNTER = new AtomicLong(); + final PolyglotEngineImpl engine; /** @@ -93,7 +96,8 @@ final class PolyglotSharingLayer { static final class Shared { - final PolyglotSourceCache sourceCache = new PolyglotSourceCache(); + final long id; + final PolyglotSourceCache sourceCache; // indexed by engine index, not static index @CompilationFinal(dimensions = 1) private final PolyglotLanguageInstance[] instances; @CompilationFinal ContextPolicy contextPolicy; @@ -109,9 +113,11 @@ static final class Shared { int claimedCount; private Shared(PolyglotEngineImpl engine, ContextPolicy contextPolicy, Map previousLanguageOptions) { + this.sourceCache = new PolyglotSourceCache(TracingSourceCacheListener.createOrNull(engine)); this.contextPolicy = contextPolicy; this.instances = new PolyglotLanguageInstance[engine.languageCount]; this.previousLanguageOptions = previousLanguageOptions; + this.id = LAYER_COUNTER.incrementAndGet(); } void updatePreinitConfig(PolyglotContextConfig config) { diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSourceCache.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSourceCache.java index 54d8c6287458..8b502ddb4ed1 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSourceCache.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotSourceCache.java @@ -49,6 +49,7 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.source.Source; @@ -57,12 +58,18 @@ final class PolyglotSourceCache { private final Cache strongCache; private final Cache weakCache; + private SourceCacheListener sourceCacheListener; - PolyglotSourceCache() { + PolyglotSourceCache(SourceCacheListener sourceCacheListener) { + this.sourceCacheListener = sourceCacheListener; this.weakCache = new WeakCache(); this.strongCache = new StrongCache(); } + void patch(SourceCacheListener listener) { + this.sourceCacheListener = listener; + } + CallTarget parseCached(PolyglotLanguageContext context, Source source, String[] argumentNames) { CallTarget target; if (source.isCached()) { @@ -79,7 +86,14 @@ CallTarget parseCached(PolyglotLanguageContext context, Source source, String[] } target = weakCache.lookup(context, source, argumentNames, true); } else { + long parseStart = 0; + if (sourceCacheListener != null) { + parseStart = System.currentTimeMillis(); + } target = parseImpl(context, argumentNames, source); + if (sourceCacheListener != null) { + sourceCacheListener.onCacheMiss(source, target, SourceCacheListener.CacheType.UNCACHED, parseStart); + } } return target; } @@ -173,22 +187,56 @@ private abstract static class Cache { abstract void listSources(PolyglotImpl polyglot, Collection source); } - private static final class StrongCache extends Cache { + static class StrongCacheValue { - private final ConcurrentHashMap sourceCache = new ConcurrentHashMap<>(); + final CallTarget target; + final AtomicLong hits = new AtomicLong(); + + StrongCacheValue(CallTarget target) { + this.target = target; + } + + } + + private final class StrongCache extends Cache { + + private final ConcurrentHashMap sourceCache = new ConcurrentHashMap<>(); @Override CallTarget lookup(PolyglotLanguageContext context, Source source, String[] argumentNames, boolean parse) { SourceKey key = new SourceKey(source, argumentNames); - CallTarget target = sourceCache.get(key); - if (target == null && parse) { - target = parseImpl(context, argumentNames, source); - CallTarget prevTarget = sourceCache.putIfAbsent(key, target); - if (prevTarget != null) { - target = prevTarget; + StrongCacheValue value = sourceCache.get(key); + if (value == null) { + if (parse) { + long parseStart = 0; + if (sourceCacheListener != null) { + parseStart = System.currentTimeMillis(); + } + try { + value = new StrongCacheValue(parseImpl(context, argumentNames, source)); + StrongCacheValue prevValue = sourceCache.putIfAbsent(key, value); + if (prevValue != null) { + value = prevValue; + } + if (sourceCacheListener != null) { + sourceCacheListener.onCacheMiss(source, value.target, SourceCacheListener.CacheType.STRONG, parseStart); + } + } catch (Throwable t) { + if (sourceCacheListener != null) { + sourceCacheListener.onCacheFail(context.context.layer, source, SourceCacheListener.CacheType.STRONG, parseStart, t); + } + throw t; + } + } else { + return null; + } + } else { + value.hits.incrementAndGet(); + if (sourceCacheListener != null) { + sourceCacheListener.onCacheHit(source, value.target, SourceCacheListener.CacheType.STRONG, value.hits.get()); } } - return target; + return value.target; } @Override @@ -205,7 +253,7 @@ void listSources(PolyglotImpl polyglot, Collection sources) { } - private static final class WeakCache extends Cache { + private final class WeakCache extends Cache { private final ConcurrentHashMap sourceCache = new ConcurrentHashMap<>(); private final ReferenceQueue deadSources = new ReferenceQueue<>(); @@ -219,17 +267,36 @@ CallTarget lookup(PolyglotLanguageContext context, Source source, String[] argum WeakCacheValue value = sourceCache.get(ref); if (value == null) { if (parse) { - value = new WeakCacheValue(parseImpl(context, argumentNames, sourceValue), sourceValue); - WeakCacheValue prev = sourceCache.putIfAbsent(ref, value); - if (prev != null) { - /* - * Parsed twice -> discard the one not in the cache. - */ - value = prev; + long parseStart = 0; + if (sourceCacheListener != null) { + parseStart = System.currentTimeMillis(); + } + try { + value = new WeakCacheValue(parseImpl(context, argumentNames, sourceValue), sourceValue); + WeakCacheValue prev = sourceCache.putIfAbsent(ref, value); + if (prev != null) { + /* + * Parsed twice -> discard the one not in the cache. + */ + value = prev; + } + if (sourceCacheListener != null) { + sourceCacheListener.onCacheMiss(source, value.target, SourceCacheListener.CacheType.WEAK, parseStart); + } + } catch (Throwable t) { + if (sourceCacheListener != null) { + sourceCacheListener.onCacheFail(context.context.layer, source, SourceCacheListener.CacheType.WEAK, parseStart, t); + } + throw t; } } else { return null; } + } else { + value.hits.incrementAndGet(); + if (sourceCacheListener != null) { + sourceCacheListener.onCacheHit(source, value.target, SourceCacheListener.CacheType.WEAK, value.hits.get()); + } } return value.target; } @@ -248,9 +315,12 @@ void listSources(PolyglotImpl polyglot, Collection sources) { } private void cleanupStaleEntries() { - WeakSourceKey sourceRef = null; + WeakSourceKey sourceRef; while ((sourceRef = (WeakSourceKey) deadSources.poll()) != null) { - sourceCache.remove(sourceRef); + WeakCacheValue value = sourceCache.remove(sourceRef); + if (value != null && sourceCacheListener != null) { + sourceCacheListener.onCacheEvict(value.source, value.target, SourceCacheListener.CacheType.WEAK, value.hits.get()); + } } } @@ -260,6 +330,7 @@ static class WeakCacheValue { final CallTarget target; final Source source; + final AtomicLong hits = new AtomicLong(); WeakCacheValue(CallTarget target, Source source) { this.target = target; diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/SourceCacheListener.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/SourceCacheListener.java new file mode 100644 index 000000000000..b84546f1e839 --- /dev/null +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/SourceCacheListener.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024, 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.polyglot; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.source.Source; + +interface SourceCacheListener { + enum CacheType { + WEAK, + STRONG, + UNCACHED + } + + void onCacheHit(Source source, CallTarget target, CacheType cacheType, long hits); + + void onCacheMiss(Source source, CallTarget target, CacheType cacheType, long startTime); + + void onCacheFail(PolyglotSharingLayer sharingLayer, Source source, CacheType cacheType, long startTime, Throwable throwable); + + void onCacheEvict(Source source, CallTarget target, CacheType cacheType, long hits); +} diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/TracingSourceCacheListener.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/TracingSourceCacheListener.java new file mode 100644 index 000000000000..cf82135bbea7 --- /dev/null +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/TracingSourceCacheListener.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2024, 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.polyglot; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.logging.Level; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.RootCallTarget; +import com.oracle.truffle.api.source.Source; + +final class TracingSourceCacheListener implements SourceCacheListener { + + private static final int MAX_SOURCE_NAME_LENGTH = 50; + private static final int MAX_ERROR_MESSAGE_LENGTH = 100; + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS").withZone(ZoneId.of("UTC")); + // @formatter:off + private static final String COMMON_PARAMS = "|Engine %-2d|Layer %-2d|CallTarget %-5d|Lang %-10s|Policy %-9s|%-8s|UTC %s"; + private static final String SOURCE_FORMAT = "0x%08x %-" + MAX_SOURCE_NAME_LENGTH + "s"; + private static final String HIT_FORMAT = "source-cache-hit " + SOURCE_FORMAT + "|Hits %12d" + COMMON_PARAMS; + private static final String MISS_FORMAT = "source-cache-miss " + SOURCE_FORMAT + "|ParseTime %4d ms" + COMMON_PARAMS; + private static final String FAIL_FORMAT = "source-cache-fail " + SOURCE_FORMAT + "|ParseTime %4d ms" + COMMON_PARAMS + "|Error %s"; + private static final String EVICT_FORMAT = "source-cache-evict " + SOURCE_FORMAT + "|Hits %12d" + COMMON_PARAMS; + // @formatter:on + + private static void log(PolyglotSharingLayer sharingLayer, String logFormat, Object... params) { + sharingLayer.engine.getEngineLogger().log(Level.INFO, String.format(logFormat, params)); + } + + private static String truncateString(String s, int maxLength) { + if (s == null || s.length() <= maxLength) { + return s; + } else { + return s.substring(0, maxLength); + } + } + + private final boolean traceSourceCacheDetails; + + private TracingSourceCacheListener(boolean traceSourceCacheDetails) { + this.traceSourceCacheDetails = traceSourceCacheDetails; + } + + static TracingSourceCacheListener createOrNull(PolyglotEngineImpl engine) { + boolean traceSourceCacheDetails = engine.engineOptionValues.get(PolyglotEngineOptions.TraceSourceCacheDetails); + boolean traceSourceCache = traceSourceCacheDetails || engine.engineOptionValues.get(PolyglotEngineOptions.TraceSourceCache); + if (traceSourceCache) { + return new TracingSourceCacheListener(traceSourceCacheDetails); + } else { + return null; + } + } + + @Override + public void onCacheHit(Source source, CallTarget target, CacheType cacheType, long hits) { + if (traceSourceCacheDetails && target instanceof RootCallTarget) { + PolyglotSharingLayer sharingLayer = (PolyglotSharingLayer) EngineAccessor.NODES.getSharingLayer(((RootCallTarget) target).getRootNode()); + log(sharingLayer, HIT_FORMAT, + source.hashCode(), + truncateString(source.getName(), MAX_SOURCE_NAME_LENGTH), + hits, + sharingLayer.engine.engineId, + sharingLayer.shared.id, + EngineAccessor.RUNTIME.getCallTargetId(target), + source.getLanguage(), + sharingLayer.getContextPolicy().name(), + cacheType, + TIME_FORMATTER.format(ZonedDateTime.now())); + } + } + + @Override + public void onCacheMiss(Source source, CallTarget target, CacheType cacheType, long startTime) { + if ((traceSourceCacheDetails || cacheType != CacheType.UNCACHED) && target instanceof RootCallTarget) { + PolyglotSharingLayer sharingLayer = (PolyglotSharingLayer) EngineAccessor.NODES.getSharingLayer(((RootCallTarget) target).getRootNode()); + log(sharingLayer, MISS_FORMAT, + source.hashCode(), + truncateString(source.getName(), MAX_SOURCE_NAME_LENGTH), + System.currentTimeMillis() - startTime, + sharingLayer.engine.engineId, + sharingLayer.shared.id, + EngineAccessor.RUNTIME.getCallTargetId(target), + source.getLanguage(), + sharingLayer.getContextPolicy().name(), + cacheType, + TIME_FORMATTER.format(ZonedDateTime.now())); + } + } + + @Override + public void onCacheFail(PolyglotSharingLayer sharingLayer, Source source, CacheType cacheType, long startTime, Throwable throwable) { + log(sharingLayer, FAIL_FORMAT, + source.hashCode(), + truncateString(source.getName(), MAX_SOURCE_NAME_LENGTH), + System.currentTimeMillis() - startTime, + sharingLayer.engine.engineId, + sharingLayer.shared.id, + 0, + source.getLanguage(), + sharingLayer.getContextPolicy().name(), + cacheType, + TIME_FORMATTER.format(ZonedDateTime.now()), + truncateString(throwable.getMessage(), MAX_ERROR_MESSAGE_LENGTH)); + } + + @Override + public void onCacheEvict(Source source, CallTarget target, CacheType cacheType, long hits) { + if (target instanceof RootCallTarget) { + PolyglotSharingLayer sharingLayer = (PolyglotSharingLayer) EngineAccessor.NODES.getSharingLayer(((RootCallTarget) target).getRootNode()); + log(sharingLayer, EVICT_FORMAT, + source.hashCode(), + truncateString(source.getName(), MAX_SOURCE_NAME_LENGTH), + hits, + sharingLayer.engine.engineId, + sharingLayer.shared.id, + EngineAccessor.RUNTIME.getCallTargetId(target), + source.getLanguage(), + sharingLayer.getContextPolicy().name(), + cacheType, + TIME_FORMATTER.format(ZonedDateTime.now())); + } + } +} 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 76085b3bfa1c..d275d67d3233 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 @@ -85,7 +85,6 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; import java.util.logging.Level; @@ -109,8 +108,6 @@ */ public final class EngineData { - private static final AtomicLong engineCounter = new AtomicLong(); - int splitLimit; int splitCount; public final long id; @@ -182,7 +179,7 @@ public final class EngineData { Objects.requireNonNull(polyglotEngine); Objects.requireNonNull(runtimeOptions); this.polyglotEngine = polyglotEngine; - this.id = engineCounter.incrementAndGet(); + this.id = OptimizedRuntimeAccessor.ENGINE.getEngineId(polyglotEngine); this.loggerFactory = loggerFactory; this.loadOptions(runtimeOptions, sandboxPolicy); 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 662032d04d68..028aafc0fabe 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 @@ -357,7 +357,7 @@ public Class getType() { private volatile WeakReference singleCallNode = NO_CALL; volatile List blockCompilations; public final long id; - private static final AtomicLong idCounter = new AtomicLong(0); + private static final AtomicLong ID_COUNTER = new AtomicLong(0); @SuppressWarnings("this-escape") protected OptimizedCallTarget(OptimizedCallTarget sourceCallTarget, RootNode rootNode) { @@ -370,7 +370,7 @@ protected OptimizedCallTarget(OptimizedCallTarget sourceCallTarget, RootNode roo // Do not adopt children of OSRRootNodes; we want to preserve the parent of the child // node(s). this.uninitializedNodeCount = uninitializedNodeCount(rootNode); - id = idCounter.getAndIncrement(); + id = ID_COUNTER.getAndIncrement(); } protected OptimizedCallTarget(EngineData engine) { @@ -382,7 +382,7 @@ protected OptimizedCallTarget(EngineData engine) { this.sourceCallTarget = null; // Do not adopt children of OSRRootNodes; we want to preserve the parent of the child // node(s). - id = idCounter.getAndIncrement(); + id = ID_COUNTER.getAndIncrement(); } private int uninitializedNodeCount(RootNode rootNode) { diff --git a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeSupport.java b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeSupport.java index fcf2e6d43192..63e92b4aec25 100644 --- a/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeSupport.java +++ b/truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedRuntimeSupport.java @@ -84,6 +84,15 @@ public RootCallTarget newCallTarget(CallTarget source, RootNode rootNode) { return OptimizedTruffleRuntime.getRuntime().createOptimizedCallTarget((OptimizedCallTarget) source, rootNode); } + @Override + public long getCallTargetId(CallTarget target) { + if (target instanceof OptimizedCallTarget) { + return ((OptimizedCallTarget) target).id; + } else { + return 0; + } + } + @Override public boolean isLoaded(CallTarget callTarget) { return ((OptimizedCallTarget) callTarget).isLoaded();