Skip to content

Commit e3e30c1

Browse files
committed
[GR-33829] Add support for the PE encoded graph cache on libgraal.
PullRequest: graal/11882
2 parents 3db6403 + 1f3bab7 commit e3e30c1

File tree

26 files changed

+441
-180
lines changed

26 files changed

+441
-180
lines changed

compiler/src/org.graalvm.compiler.replacements.test/src/org/graalvm/compiler/replacements/test/PEGraphDecoderTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ private StructuredGraph test(String methodName, EconomicMap<ResolvedJavaMethod,
188188
registerPlugins(graphBuilderConfig.getPlugins().getInvocationPlugins());
189189
targetGraph = new StructuredGraph.Builder(debug.getOptions(), debug, AllowAssumptions.YES).method(testMethod).build();
190190
CachingPEGraphDecoder decoder = new CachingPEGraphDecoder(getTarget().arch, targetGraph, getProviders(), graphBuilderConfig, OptimisticOptimizations.NONE, AllowAssumptions.YES,
191-
null, null, new InlineInvokePlugin[]{new InlineAll()}, null, null, null, null, null, graphCache, false);
191+
null, null, new InlineInvokePlugin[]{new InlineAll()}, null, null, null, null, null, graphCache, () -> null, false);
192192

193193
decoder.decode(testMethod, false, false);
194194
debug.dump(DebugContext.BASIC_LEVEL, targetGraph, "Target Graph");

compiler/src/org.graalvm.compiler.replacements/src/org/graalvm/compiler/replacements/CachingPEGraphDecoder.java

+116-7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static jdk.vm.ci.services.Services.IS_IN_NATIVE_IMAGE;
2828

2929
import java.util.concurrent.ConcurrentHashMap;
30+
import java.util.function.Supplier;
3031

3132
import org.graalvm.collections.EconomicMap;
3233
import org.graalvm.compiler.bytecode.BytecodeProvider;
@@ -57,7 +58,9 @@
5758
import org.graalvm.compiler.phases.util.Providers;
5859

5960
import jdk.vm.ci.code.Architecture;
61+
import jdk.vm.ci.meta.Assumptions;
6062
import jdk.vm.ci.meta.ResolvedJavaMethod;
63+
import jdk.vm.ci.meta.ResolvedJavaType;
6164

6265
/**
6366
* A graph decoder that provides all necessary encoded graphs on-the-fly (by parsing the methods and
@@ -71,14 +74,17 @@ public class CachingPEGraphDecoder extends PEGraphDecoder {
7174
protected final GraphBuilderConfiguration graphBuilderConfig;
7275
protected final OptimisticOptimizations optimisticOpts;
7376
private final AllowAssumptions allowAssumptions;
74-
private final EconomicMap<ResolvedJavaMethod, EncodedGraph> graphCache;
77+
private final EconomicMap<ResolvedJavaMethod, EncodedGraph> persistentGraphCache;
78+
private final EconomicMap<ResolvedJavaMethod, EncodedGraph> localGraphCache;
79+
private final Supplier<AutoCloseable> createPersistentCachedGraphScope;
7580
private final BasePhase<? super CoreProviders> postParsingPhase;
7681

7782
public CachingPEGraphDecoder(Architecture architecture, StructuredGraph graph, Providers providers, GraphBuilderConfiguration graphBuilderConfig, OptimisticOptimizations optimisticOpts,
7883
AllowAssumptions allowAssumptions, LoopExplosionPlugin loopExplosionPlugin, InvocationPlugins invocationPlugins, InlineInvokePlugin[] inlineInvokePlugins,
7984
ParameterPlugin parameterPlugin,
8085
NodePlugin[] nodePlugins, ResolvedJavaMethod peRootForInlining, SourceLanguagePositionProvider sourceLanguagePositionProvider,
81-
BasePhase<? super CoreProviders> postParsingPhase, EconomicMap<ResolvedJavaMethod, EncodedGraph> graphCache, boolean needsExplicitException) {
86+
BasePhase<? super CoreProviders> postParsingPhase, EconomicMap<ResolvedJavaMethod, EncodedGraph> persistentGraphCache, Supplier<AutoCloseable> createPersistentCachedGraphScope,
87+
boolean needsExplicitException) {
8288
super(architecture, graph, providers, loopExplosionPlugin,
8389
invocationPlugins, inlineInvokePlugins, parameterPlugin, nodePlugins, peRootForInlining, sourceLanguagePositionProvider,
8490
new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), needsExplicitException);
@@ -87,8 +93,10 @@ public CachingPEGraphDecoder(Architecture architecture, StructuredGraph graph, P
8793
this.graphBuilderConfig = graphBuilderConfig;
8894
this.optimisticOpts = optimisticOpts;
8995
this.allowAssumptions = allowAssumptions;
90-
this.graphCache = graphCache;
9196
this.postParsingPhase = postParsingPhase;
97+
this.persistentGraphCache = persistentGraphCache;
98+
this.createPersistentCachedGraphScope = createPersistentCachedGraphScope;
99+
this.localGraphCache = EconomicMap.create();
92100
}
93101

94102
protected GraphBuilderPhase.Instance createGraphBuilderPhaseInstance(IntrinsicContext initialIntrinsicContext) {
@@ -120,7 +128,7 @@ private EncodedGraph createGraph(ResolvedJavaMethod method, BytecodeProvider int
120128
}
121129

122130
EncodedGraph encodedGraph = GraphEncoder.encodeSingleGraph(graphToEncode, architecture);
123-
graphCache.put(method, encodedGraph);
131+
persistentGraphCache.put(method, encodedGraph);
124132
return encodedGraph;
125133
}
126134

@@ -152,13 +160,114 @@ private StructuredGraph buildGraph(ResolvedJavaMethod method, BytecodeProvider i
152160
return graphToEncode;
153161
}
154162

163+
private static boolean verifyAssumptions(EncodedGraph graph) {
164+
Assumptions assumptions = graph.getAssumptions();
165+
if (assumptions == null || assumptions.isEmpty()) {
166+
return true; // verified
167+
}
168+
for (Assumptions.Assumption assumption : assumptions) {
169+
if (assumption instanceof Assumptions.LeafType) {
170+
Assumptions.LeafType leafType = (Assumptions.LeafType) assumption;
171+
/*
172+
* LeafType cannot be fully verified because the assumption doesn't imply that the
173+
* type is (also) concrete. We check a common case (leaf + concrete type).
174+
*/
175+
Assumptions.AssumptionResult<ResolvedJavaType> assumptionResult = leafType.context.findLeafConcreteSubtype();
176+
if (assumptionResult != null) {
177+
ResolvedJavaType candidate = assumptionResult.getResult();
178+
if (!leafType.context.equals(candidate)) {
179+
return false;
180+
}
181+
}
182+
} else if (assumption instanceof Assumptions.ConcreteSubtype) {
183+
Assumptions.ConcreteSubtype concreteSubtype = (Assumptions.ConcreteSubtype) assumption;
184+
/*
185+
* ConcreteSubtype cannot be fully verified because the assumption doesn't imply
186+
* that the concrete subtype is (also) a leaf. We check a common case (leaf +
187+
* concrete type).
188+
*/
189+
Assumptions.AssumptionResult<ResolvedJavaType> assumptionResult = concreteSubtype.context.findLeafConcreteSubtype();
190+
if (assumptionResult != null) {
191+
ResolvedJavaType candidate = assumptionResult.getResult();
192+
/*
193+
* No equality check here because the ConcreteSubtype assumption allows
194+
* non-concrete subtypes for interfaces.
195+
*/
196+
if (!concreteSubtype.subtype.isAssignableFrom(candidate)) {
197+
return false;
198+
}
199+
}
200+
} else if (assumption instanceof Assumptions.ConcreteMethod) {
201+
Assumptions.ConcreteMethod concreteMethod = (Assumptions.ConcreteMethod) assumption;
202+
/*
203+
* ConcreteMethod is the only assumption that can be verified since it matches
204+
* findUniqueConcreteMethod semantics. If the assumption cannot be retrieved
205+
* (findUniqueConcreteMethod returns null) then it was invalidated.
206+
*/
207+
Assumptions.AssumptionResult<ResolvedJavaMethod> assumptionResult = concreteMethod.context.findUniqueConcreteMethod(concreteMethod.method);
208+
if (assumptionResult == null || !concreteMethod.impl.equals(assumptionResult.getResult())) {
209+
return false;
210+
}
211+
} else if (assumption instanceof Assumptions.NoFinalizableSubclass || assumption instanceof Assumptions.CallSiteTargetValue ||
212+
"org.graalvm.compiler.truffle.compiler.nodes.TruffleAssumption".equals(assumption.getClass().getName())) {
213+
/*
214+
* These assumptions cannot be (even partially) verified. The cached graph will be
215+
* invalidated on code installation.
216+
*/
217+
} else {
218+
throw GraalError.shouldNotReachHere("unexpected assumption " + assumption);
219+
}
220+
}
221+
return true;
222+
}
223+
224+
@SuppressWarnings({"unused", "try"})
225+
private EncodedGraph lookupOrCreatePersistentEncodedGraph(ResolvedJavaMethod method, BytecodeProvider intrinsicBytecodeProvider, boolean isSubstitution,
226+
boolean trackNodeSourcePosition) {
227+
EncodedGraph result = persistentGraphCache.get(method);
228+
if (result == null && method.hasBytecodes()) {
229+
try (AutoCloseable scope = createPersistentCachedGraphScope.get()) {
230+
// Encoded graphs that can be cached across compilations are wrapped by "scopes"
231+
// provided by createCachedGraphScope.
232+
result = createGraph(method, intrinsicBytecodeProvider, isSubstitution);
233+
} catch (Throwable ex) {
234+
throw debug.handle(ex);
235+
}
236+
}
237+
return result;
238+
}
239+
155240
@Override
156241
protected EncodedGraph lookupEncodedGraph(ResolvedJavaMethod method, BytecodeProvider intrinsicBytecodeProvider, boolean isSubstitution,
157242
boolean trackNodeSourcePosition) {
158-
EncodedGraph result = graphCache.get(method);
159-
if (result == null && method.hasBytecodes()) {
160-
result = createGraph(method, intrinsicBytecodeProvider, isSubstitution);
243+
// Graph's assumptions are fresh or validated recently.
244+
EncodedGraph result = localGraphCache.get(method);
245+
if (result != null) {
246+
return result;
161247
}
248+
249+
result = persistentGraphCache.get(method);
250+
if (result == null) {
251+
// Embedded assumptions in a fresh encoded graph should be up-to-date, so no need to
252+
// validate them.
253+
result = lookupOrCreatePersistentEncodedGraph(method, intrinsicBytecodeProvider, isSubstitution, trackNodeSourcePosition);
254+
if (result != null) {
255+
localGraphCache.put(method, result);
256+
}
257+
} else if (!verifyAssumptions(result)) {
258+
// Cached graph has invalid assumptions, drop from persistent cache and re-parse.
259+
persistentGraphCache.removeKey(method);
260+
// Embedded assumptions in a fresh encoded graph should be up-to-date, so no need to
261+
// validate them.
262+
result = lookupOrCreatePersistentEncodedGraph(method, intrinsicBytecodeProvider, isSubstitution, trackNodeSourcePosition);
263+
if (result != null) {
264+
localGraphCache.put(method, result);
265+
}
266+
} else {
267+
// Assumptions validated, avoid further checks.
268+
localGraphCache.put(method, result);
269+
}
270+
162271
return result;
163272
}
164273
}

compiler/src/org.graalvm.compiler.truffle.common.hotspot.libgraal/src/org/graalvm/compiler/truffle/common/hotspot/libgraal/TruffleToLibGraal.java

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ enum Id {
8282
OpenDebugContext,
8383
OpenDebugContextScope,
8484
PendingTransferToInterpreterOffset,
85+
PurgePartialEvaluationCaches,
8586
Shutdown;
8687
}
8788
}

compiler/src/org.graalvm.compiler.truffle.common.hotspot/src/org/graalvm/compiler/truffle/common/hotspot/HotSpotTruffleCompiler.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,5 @@ public interface HotSpotTruffleCompiler extends TruffleCompiler {
4848
/**
4949
* Releases caches used for PE/compilation.
5050
*/
51-
default void purgeCaches() {
52-
// nop
53-
}
51+
void purgePartialEvaluationCaches();
5452
}

compiler/src/org.graalvm.compiler.truffle.compiler.hotspot.libgraal/src/org/graalvm/compiler/truffle/compiler/hotspot/libgraal/TruffleToLibGraalEntryPoints.java

+15
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import static org.graalvm.compiler.truffle.common.hotspot.libgraal.TruffleToLibGraal.Id.OpenDebugContext;
6060
import static org.graalvm.compiler.truffle.common.hotspot.libgraal.TruffleToLibGraal.Id.OpenDebugContextScope;
6161
import static org.graalvm.compiler.truffle.common.hotspot.libgraal.TruffleToLibGraal.Id.PendingTransferToInterpreterOffset;
62+
import static org.graalvm.compiler.truffle.common.hotspot.libgraal.TruffleToLibGraal.Id.PurgePartialEvaluationCaches;
6263
import static org.graalvm.compiler.truffle.common.hotspot.libgraal.TruffleToLibGraal.Id.Shutdown;
6364
import static org.graalvm.jniutils.JNIUtil.GetArrayLength;
6465
import static org.graalvm.jniutils.JNIUtil.GetByteArrayElements;
@@ -723,6 +724,20 @@ public static void dumpChannelClose(JNIEnv env, JClass hsClass, @CEntryPoint.Iso
723724
}
724725
}
725726

727+
@TruffleToLibGraal(PurgePartialEvaluationCaches)
728+
@CEntryPoint(name = "Java_org_graalvm_compiler_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_purgePartialEvaluationCaches")
729+
@SuppressWarnings({"unused", "try"})
730+
public static void purgePartialEvaluationCaches(JNIEnv env, JClass hsClass, @CEntryPoint.IsolateThreadContext long isolateThreadId, long compilerHandle) {
731+
try (JNIMethodScope s = LibGraalUtil.openScope(TruffleToLibGraalEntryPoints.class, PurgePartialEvaluationCaches, env)) {
732+
HotSpotTruffleCompilerImpl compiler = LibGraalObjectHandles.resolve(compilerHandle, HotSpotTruffleCompilerImpl.class);
733+
if (compiler != null) {
734+
compiler.purgePartialEvaluationCaches();
735+
}
736+
} catch (Throwable t) {
737+
JNIExceptionWrapper.throwInHotSpot(env, t);
738+
}
739+
}
740+
726741
@TruffleToLibGraal(GetExecutionID)
727742
@CEntryPoint(name = "Java_org_graalvm_compiler_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_getExecutionID")
728743
@SuppressWarnings({"unused", "try"})

compiler/src/org.graalvm.compiler.truffle.compiler.hotspot/src/org/graalvm/compiler/truffle/compiler/hotspot/HotSpotPartialEvaluator.java

+37-34
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,17 @@
2424
*/
2525
package org.graalvm.compiler.truffle.compiler.hotspot;
2626

27-
import java.util.Collections;
28-
import java.util.LinkedHashMap;
29-
import java.util.Map;
3027
import java.util.concurrent.ConcurrentHashMap;
3128
import java.util.concurrent.atomic.AtomicReference;
29+
import java.util.function.Supplier;
3230

3331
import org.graalvm.collections.EconomicMap;
32+
import org.graalvm.compiler.hotspot.HotSpotGraalServices;
3433
import org.graalvm.compiler.nodes.EncodedGraph;
3534
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
3635
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
3736
import org.graalvm.compiler.truffle.compiler.PartialEvaluator;
3837
import org.graalvm.compiler.truffle.compiler.TruffleCompilerConfiguration;
39-
import org.graalvm.compiler.truffle.options.PolyglotCompilerOptions;
4038
import org.graalvm.options.OptionValues;
4139

4240
import jdk.vm.ci.meta.ResolvedJavaMethod;
@@ -45,16 +43,14 @@ public final class HotSpotPartialEvaluator extends PartialEvaluator {
4543

4644
private final AtomicReference<EconomicMap<ResolvedJavaMethod, EncodedGraph>> graphCacheRef;
4745

48-
public boolean isEncodedGraphCacheEnabled() {
49-
return encodedGraphCacheCapacity != 0;
50-
}
51-
52-
private int encodedGraphCacheCapacity;
5346
private int jvmciReservedReference0Offset = -1;
5447

48+
private boolean disableEncodedGraphCachePurges;
49+
5550
public HotSpotPartialEvaluator(TruffleCompilerConfiguration config, GraphBuilderConfiguration configForRoot) {
5651
super(config, configForRoot, new HotSpotKnownTruffleTypes(config.lastTier().providers().getMetaAccess()));
5752
this.graphCacheRef = new AtomicReference<>();
53+
this.disableEncodedGraphCachePurges = false;
5854
}
5955

6056
void setJvmciReservedReference0Offset(int jvmciReservedReference0Offset) {
@@ -68,7 +64,6 @@ public int getJvmciReservedReference0Offset() {
6864
@Override
6965
protected void initialize(OptionValues options) {
7066
super.initialize(options);
71-
encodedGraphCacheCapacity = options.get(PolyglotCompilerOptions.EncodedGraphCacheCapacity);
7267
}
7368

7469
@Override
@@ -78,42 +73,50 @@ protected void registerGraphBuilderInvocationPlugins(InvocationPlugins invocatio
7873
(HotSpotKnownTruffleTypes) getKnownTruffleTypes());
7974
}
8075

81-
@SuppressWarnings("serial")
82-
private Map<ResolvedJavaMethod, EncodedGraph> createEncodedGraphMap() {
83-
if (encodedGraphCacheCapacity < 0) {
84-
// Unbounded cache.
85-
return new ConcurrentHashMap<>();
86-
}
87-
88-
// Access-based LRU bounded cache. The overhead of the synchronized map is negligible
89-
// compared to the cost of re-parsing the graphs.
90-
return Collections.synchronizedMap(
91-
new LinkedHashMap<ResolvedJavaMethod, EncodedGraph>(16, 0.75f, true) {
92-
@Override
93-
protected boolean removeEldestEntry(Map.Entry<ResolvedJavaMethod, EncodedGraph> eldest) {
94-
// encodedGraphCacheCapacity < 0 => unbounded capacity
95-
return (encodedGraphCacheCapacity >= 0) && size() > encodedGraphCacheCapacity;
96-
}
97-
});
98-
}
99-
10076
@Override
101-
public EconomicMap<ResolvedJavaMethod, EncodedGraph> getOrCreateEncodedGraphCache() {
102-
if (encodedGraphCacheCapacity == 0) {
77+
public EconomicMap<ResolvedJavaMethod, EncodedGraph> getOrCreateEncodedGraphCache(boolean persistentEncodedGraphCache) {
78+
if (!persistentEncodedGraphCache) {
10379
// The encoded graph cache is disabled across different compilations. The returned map
10480
// can still be used and propagated within the same compilation unit.
105-
return super.getOrCreateEncodedGraphCache();
81+
return super.getOrCreateEncodedGraphCache(persistentEncodedGraphCache);
10682
}
10783
EconomicMap<ResolvedJavaMethod, EncodedGraph> cache;
10884
do {
10985
cache = graphCacheRef.get();
11086
} while (cache == null &&
111-
!graphCacheRef.compareAndSet(null, cache = EconomicMap.wrapMap(createEncodedGraphMap())));
87+
!graphCacheRef.compareAndSet(null, cache = EconomicMap.wrapMap(new ConcurrentHashMap<>())));
11288
assert cache != null;
11389
return cache;
11490
}
11591

92+
/**
93+
* Called in unit-tests via reflection.
94+
*/
11695
public void purgeEncodedGraphCache() {
117-
graphCacheRef.set(null);
96+
// Disabling purges only for tests.
97+
if (!disableEncodedGraphCachePurges) {
98+
graphCacheRef.set(null);
99+
}
100+
}
101+
102+
/**
103+
* Used only in unit-tests, to avoid transient failures caused by multiple compiler threads
104+
* racing to purge the cache. Called reflectively from EncodedGraphCacheTest.
105+
*/
106+
public boolean disableEncodedGraphCachePurges(boolean value) {
107+
boolean oldValue = disableEncodedGraphCachePurges;
108+
disableEncodedGraphCachePurges = value;
109+
return oldValue;
110+
}
111+
112+
@Override
113+
protected Supplier<AutoCloseable> getCreateCachedGraphScope(boolean persistentEncodedGraphCache) {
114+
if (persistentEncodedGraphCache) {
115+
// The interpreter graphs may be cached across compilations, keep JavaConstants
116+
// references to the application heap alive in the libgraal global scope.
117+
return HotSpotGraalServices::enterGlobalCompilationContext;
118+
} else {
119+
return super.getCreateCachedGraphScope(persistentEncodedGraphCache);
120+
}
118121
}
119122
}

0 commit comments

Comments
 (0)