Skip to content

Commit db5d4d2

Browse files
davleopofangerer
authored andcommitted
Conceptually support shared arenas in runtime compilation
1 parent fe9c585 commit db5d4d2

File tree

14 files changed

+324
-70
lines changed

14 files changed

+324
-70
lines changed

substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignAPIPredicates.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,24 @@ public boolean getAsBoolean() {
6262
}
6363
}
6464

65-
@Platforms(Platform.HOSTED_ONLY.class)
6665
public static final class SharedArenasEnabled implements BooleanSupplier {
67-
public static boolean getValue() {
68-
return SubstrateOptions.isForeignAPIEnabled() && SubstrateOptions.SharedArenaSupport.getValue();
66+
private static final String VECTOR_API_SUPPORT_OPTION_NAME = SubstrateOptionsParser.commandArgument(SubstrateOptions.VectorAPISupport, "-");
67+
private static final String SHARED_ARENA_SUPPORT_OPTION_NAME = SubstrateOptionsParser.commandArgument(SubstrateOptions.SharedArenaSupport, "-");
68+
69+
@Platforms(Platform.HOSTED_ONLY.class)
70+
SharedArenasEnabled() {
6971
}
7072

7173
@Override
7274
public boolean getAsBoolean() {
73-
return SharedArenasEnabled.getValue();
75+
return SubstrateOptions.isSharedArenaSupportEnabled();
76+
}
77+
78+
public static RuntimeException vectorAPIUnsupported() {
79+
assert !SubstrateOptions.isSharedArenaSupportEnabled();
80+
throw VMError.unsupportedFeature("Support for Arena.ofShared is not available if Vector API support is enabled." +
81+
"Either disable Vector API support using " + VECTOR_API_SUPPORT_OPTION_NAME +
82+
" or replace usages of Arena.ofShared with Arena.ofAuto and disable shared arena support using " + SHARED_ARENA_SUPPORT_OPTION_NAME + ".");
7483
}
7584
}
7685

@@ -83,11 +92,11 @@ public static final class SharedArenasDisabled implements BooleanSupplier {
8392

8493
@Override
8594
public boolean getAsBoolean() {
86-
return !SharedArenasEnabled.getValue();
95+
return !SubstrateOptions.isSharedArenaSupportEnabled();
8796
}
8897

8998
public static RuntimeException fail() {
90-
assert !SharedArenasEnabled.getValue();
99+
assert !SubstrateOptions.isSharedArenaSupportEnabled();
91100
throw VMError.unsupportedFeature("Support for Arena.ofShared is not active: enable with " + SHARED_ARENA_SUPPORT_OPTION_NAME);
92101
}
93102
}

substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
import java.util.Map;
3737
import java.util.function.BiConsumer;
3838

39-
import com.oracle.svm.core.util.ImageHeapMap;
4039
import org.graalvm.collections.EconomicMap;
40+
import org.graalvm.collections.EconomicSet;
4141
import org.graalvm.collections.Pair;
4242
import org.graalvm.nativeimage.ImageSingletons;
4343
import org.graalvm.nativeimage.Platform;
@@ -54,12 +54,14 @@
5454
import com.oracle.svm.core.SubstrateUtil;
5555
import com.oracle.svm.core.Uninterruptible;
5656
import com.oracle.svm.core.c.InvokeJavaFunctionPointer;
57+
import com.oracle.svm.core.foreign.phases.SubstrateOptimizeSharedArenaAccessPhase.OptimizeSharedArenaConfig;
5758
import com.oracle.svm.core.headers.LibC;
5859
import com.oracle.svm.core.headers.WindowsAPIs;
5960
import com.oracle.svm.core.image.DisallowedImageHeapObjects.DisallowedObjectReporter;
6061
import com.oracle.svm.core.snippets.SnippetRuntime;
6162
import com.oracle.svm.core.snippets.SubstrateForeignCallTarget;
6263
import com.oracle.svm.core.util.BasedOnJDKFile;
64+
import com.oracle.svm.core.util.ImageHeapMap;
6365
import com.oracle.svm.core.util.VMError;
6466

6567
import jdk.graal.compiler.api.replacements.Fold;
@@ -68,8 +70,10 @@
6870
import jdk.internal.foreign.MemorySessionImpl;
6971
import jdk.internal.foreign.abi.CapturableState;
7072
import jdk.internal.foreign.abi.LinkerOptions;
73+
import jdk.vm.ci.meta.ResolvedJavaMethod;
74+
import jdk.vm.ci.meta.ResolvedJavaType;
7175

72-
public class ForeignFunctionsRuntime implements ForeignSupport {
76+
public class ForeignFunctionsRuntime implements ForeignSupport, OptimizeSharedArenaConfig {
7377
@Fold
7478
public static ForeignFunctionsRuntime singleton() {
7579
return ImageSingletons.lookup(ForeignFunctionsRuntime.class);
@@ -80,6 +84,8 @@ public static ForeignFunctionsRuntime singleton() {
8084
private final EconomicMap<NativeEntryPointInfo, FunctionPointerHolder> downcallStubs = ImageHeapMap.create("downcallStubs");
8185
private final EconomicMap<Pair<DirectMethodHandleDesc, JavaEntryPointInfo>, FunctionPointerHolder> directUpcallStubs = ImageHeapMap.create("directUpcallStubs");
8286
private final EconomicMap<JavaEntryPointInfo, FunctionPointerHolder> upcallStubs = ImageHeapMap.create("upcallStubs");
87+
private final EconomicSet<ResolvedJavaType> neverAccessesSharedArenaTypes = EconomicSet.create();
88+
private final EconomicSet<ResolvedJavaMethod> neverAccessesSharedArenaMethods = EconomicSet.create();
8389

8490
private final Map<Long, TrampolineSet> trampolines = new HashMap<>();
8591
private TrampolineSet currentTrampolineSet;
@@ -151,6 +157,16 @@ public boolean addDirectUpcallStubPointer(DirectMethodHandleDesc desc, JavaEntry
151157
return directUpcallStubs.putIfAbsent(key, new FunctionPointerHolder(ptr)) == null;
152158
}
153159

160+
@Platforms(Platform.HOSTED_ONLY.class)
161+
public void registerSafeArenaAccessorClass(ResolvedJavaType type) {
162+
neverAccessesSharedArenaTypes.add(type);
163+
}
164+
165+
@Platforms(Platform.HOSTED_ONLY.class)
166+
public void registerSafeArenaAccessorMethod(ResolvedJavaMethod method) {
167+
neverAccessesSharedArenaMethods.add(method);
168+
}
169+
154170
/**
155171
* We'd rather report the function descriptor than the native method type, but we don't have it
156172
* available here. One could intercept this exception in
@@ -368,6 +384,17 @@ public static void captureCallState(int statesToCapture, CIntPointer captureBuff
368384
@Platforms(Platform.HOSTED_ONLY.class)//
369385
public static final SnippetRuntime.SubstrateForeignCallDescriptor CAPTURE_CALL_STATE = SnippetRuntime.findForeignCall(ForeignFunctionsRuntime.class,
370386
"captureCallState", HAS_SIDE_EFFECT, LocationIdentity.any());
387+
388+
@Override
389+
public boolean isSafeCallee(ResolvedJavaMethod method) {
390+
if (neverAccessesSharedArenaMethods.contains(method)) {
391+
return true;
392+
}
393+
if (neverAccessesSharedArenaTypes.contains(method.getDeclaringClass())) {
394+
return true;
395+
}
396+
return false;
397+
}
371398
}
372399

373400
interface StubPointer extends CFunctionPointer {

substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/SubstrateForeignUtil.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ExceptionInputNode;
3232
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ExceptionPathNode;
3333
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.RegularPathNode;
34+
import com.oracle.svm.core.util.VMError;
3435
import com.oracle.svm.util.LogUtils;
3536

3637
import jdk.graal.compiler.api.directives.GraalDirectives;
@@ -69,6 +70,24 @@ public static void checkSession(MemorySessionImpl session) {
6970
}
7071
}
7172

73+
/**
74+
* A decorator method for {@link MemorySessionImpl#checkValidStateRaw()} which will fail if a
75+
* shared session is passed. We use this method instead of the decorated one in runtime
76+
* compiled @Scoped-annotated methods (and their deopt targets) to fail if shared sessions are
77+
* used in runtime compiled methods (GR-66841).
78+
*/
79+
@AlwaysInline("factored out only for readability")
80+
public static void checkValidStateRawInRuntimeCompiledCode(MemorySessionImpl session) {
81+
/*
82+
* A closeable session without an owner thread is a shared session (i.e. it can be closed
83+
* concurrently).
84+
*/
85+
if (session.isCloseable() && session.ownerThread() == null) {
86+
throw VMError.unsupportedFeature("Invalid session object. Using a shared session in runtime compiled methods is not supported.");
87+
}
88+
session.checkValidStateRaw();
89+
}
90+
7291
/**
7392
* Handles exceptions related to memory sessions within a specific arena scope.
7493
*

substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
import com.oracle.svm.core.util.BasedOnJDKFile;
4040

4141
import jdk.internal.access.foreign.MappedMemoryUtilsProxy;
42+
import jdk.internal.foreign.AbstractMemorySegmentImpl;
4243
import jdk.internal.foreign.MemorySessionImpl;
4344
import jdk.internal.misc.ScopedMemoryAccess;
4445
import jdk.internal.misc.ScopedMemoryAccess.ScopedAccessError;
46+
import jdk.internal.vm.vector.VectorSupport;
4547

4648
/**
4749
* Support for shared arenas on SVM:
@@ -184,6 +186,53 @@ public void forceInternal(MemorySessionImpl session, MappedMemoryUtilsProxy mapp
184186
}
185187
}
186188

189+
@SuppressWarnings("unused")
190+
@Substitute
191+
@TargetElement(onlyWith = SharedArenasEnabled.class)
192+
@AlwaysInline("Safepoints must be visible in caller")
193+
private static <V extends VectorSupport.Vector<E>, E, S extends VectorSupport.VectorSpecies<E>> V loadFromMemorySegmentScopedInternal(MemorySessionImpl session,
194+
Class<? extends V> vmClass, Class<E> e, int length,
195+
AbstractMemorySegmentImpl msp, long offset,
196+
S s,
197+
VectorSupport.LoadOperation<AbstractMemorySegmentImpl, V, S> defaultImpl) {
198+
throw SharedArenasEnabled.vectorAPIUnsupported();
199+
}
200+
201+
@SuppressWarnings("unused")
202+
@Substitute
203+
@TargetElement(onlyWith = SharedArenasEnabled.class)
204+
@AlwaysInline("Safepoints must be visible in caller")
205+
private static <V extends VectorSupport.Vector<E>, E, S extends VectorSupport.VectorSpecies<E>, M extends VectorSupport.VectorMask<E>> V loadFromMemorySegmentMaskedScopedInternal(
206+
MemorySessionImpl session, Class<? extends V> vmClass,
207+
Class<M> maskClass, Class<E> e, int length,
208+
AbstractMemorySegmentImpl msp, long offset, M m,
209+
S s, int offsetInRange,
210+
VectorSupport.LoadVectorMaskedOperation<AbstractMemorySegmentImpl, V, S, M> defaultImpl) {
211+
throw SharedArenasEnabled.vectorAPIUnsupported();
212+
}
213+
214+
@SuppressWarnings("unused")
215+
@Substitute
216+
@TargetElement(onlyWith = SharedArenasEnabled.class)
217+
@AlwaysInline("Safepoints must be visible in caller")
218+
public static <V extends VectorSupport.Vector<E>, E> void storeIntoMemorySegment(Class<? extends V> vmClass, Class<E> e, int length,
219+
V v,
220+
AbstractMemorySegmentImpl msp, long offset,
221+
VectorSupport.StoreVectorOperation<AbstractMemorySegmentImpl, V> defaultImpl) {
222+
throw SharedArenasEnabled.vectorAPIUnsupported();
223+
}
224+
225+
@SuppressWarnings("unused")
226+
@Substitute
227+
@TargetElement(onlyWith = SharedArenasEnabled.class)
228+
@AlwaysInline("Safepoints must be visible in caller")
229+
public static <V extends VectorSupport.Vector<E>, E, M extends VectorSupport.VectorMask<E>> void storeIntoMemorySegmentMasked(Class<? extends V> vmClass, Class<M> maskClass, Class<E> e,
230+
int length, V v, M m,
231+
AbstractMemorySegmentImpl msp, long offset,
232+
VectorSupport.StoreVectorMaskedOperation<AbstractMemorySegmentImpl, V, M> defaultImpl) {
233+
throw SharedArenasEnabled.vectorAPIUnsupported();
234+
}
235+
187236
/**
188237
* This method synchronizes with all other Java threads in order to be able to safely close the
189238
* session.
Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* or visit www.oracle.com if you need additional information or have any
2323
* questions.
2424
*/
25-
package com.oracle.svm.hosted.foreign.phases;
25+
package com.oracle.svm.core.foreign.phases;
2626

2727
import static jdk.graal.compiler.debug.DebugContext.VERY_DETAILED_LEVEL;
2828

@@ -39,17 +39,13 @@
3939
import org.graalvm.collections.Equivalence;
4040
import org.graalvm.word.LocationIdentity;
4141

42-
import com.oracle.svm.core.Uninterruptible;
4342
import com.oracle.svm.core.nodes.ClusterNode;
4443
import com.oracle.svm.core.nodes.foreign.MemoryArenaValidInScopeNode;
4544
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ClusterBeginNode;
4645
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ExceptionInputNode;
4746
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ExceptionPathNode;
4847
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.RegularPathNode;
4948
import com.oracle.svm.core.nodes.foreign.ScopedMethodNode;
50-
import com.oracle.svm.hosted.foreign.ForeignFunctionsFeature;
51-
import com.oracle.svm.hosted.meta.HostedMethod;
52-
import com.oracle.svm.hosted.meta.HostedType;
5349

5450
import jdk.graal.compiler.core.common.cfg.CFGLoop;
5551
import jdk.graal.compiler.debug.Assertions;
@@ -346,10 +342,24 @@
346342
*/
347343
public class SubstrateOptimizeSharedArenaAccessPhase extends BasePhase<MidTierContext> implements RecursivePhase {
348344

345+
public interface OptimizeSharedArenaConfig {
346+
/**
347+
* Tests if calls to the given callee may remain in the critical region of
348+
* an @Scoped-annotated method. Usually, this is the case if (1) the callee does not do any
349+
* safepoint checks (recursively), or (2) the callee does not access native memory of a
350+
* shared arena AND exits with an exception. The second case normally covers methods that
351+
* are part of the path that checks the 'checkValidStateRaw' and throws an exception
352+
* otherwise.
353+
*/
354+
boolean isSafeCallee(ResolvedJavaMethod method);
355+
}
356+
349357
final CanonicalizerPhase canonicalizer;
358+
final OptimizeSharedArenaConfig config;
350359

351-
public SubstrateOptimizeSharedArenaAccessPhase(CanonicalizerPhase canonicalizer) {
360+
public SubstrateOptimizeSharedArenaAccessPhase(CanonicalizerPhase canonicalizer, OptimizeSharedArenaConfig config) {
352361
this.canonicalizer = canonicalizer;
362+
this.config = config;
353363
}
354364

355365
@Override
@@ -638,13 +648,20 @@ protected void run(StructuredGraph graph, MidTierContext context) {
638648
cleanupClusterNodes(graph, context, insertSessionChecks(graph, context));
639649
}
640650

641-
private static EconomicSet<DominatedCall> insertSessionChecks(StructuredGraph graph, MidTierContext context) {
651+
private EconomicSet<DominatedCall> insertSessionChecks(StructuredGraph graph, MidTierContext context) {
652+
if (graph.getNodes().filter(ScopedMethodNode.class).count() == 0) {
653+
/*
654+
* We are, for whatever reason, compiling the exception handler template method, we do
655+
* not verify no calls and we do not duplicate any session checks inside.
656+
*/
657+
return null;
658+
}
642659
ControlFlowGraph cfg = ControlFlowGraph.newBuilder(graph).modifiableBlocks(true).connectBlocks(true).computeFrequency(true).computeLoops(true).computeDominators(true)
643660
.computePostdominators(true)
644661
.build();
645662
// Compute the graph with all the necessary data about scoped memory accesses.
646663
EconomicSet<DominatedCall> calls = EconomicSet.create();
647-
EconomicMap<Node, List<ScopedAccess>> sugaredGraph = enumerateScopedAccesses(cfg, context, calls);
664+
EconomicMap<Node, List<ScopedAccess>> sugaredGraph = enumerateScopedAccesses(config, cfg, context, calls);
648665
if (sugaredGraph != null) {
649666
ReentrantBlockIterator.apply(new MinimalSessionChecks(graph, sugaredGraph, cfg, calls), cfg.getStartBlock());
650667
}
@@ -848,7 +865,8 @@ record DominatedCall(MemoryArenaValidInScopeNode defNode, Invoke invoke) {
848865

849866
}
850867

851-
private static EconomicMap<Node, List<ScopedAccess>> enumerateScopedAccesses(ControlFlowGraph cfg, MidTierContext context, EconomicSet<DominatedCall> dominatedCalls) {
868+
private static EconomicMap<Node, List<ScopedAccess>> enumerateScopedAccesses(OptimizeSharedArenaConfig config, ControlFlowGraph cfg, MidTierContext context,
869+
EconomicSet<DominatedCall> dominatedCalls) {
852870
EconomicMap<Node, List<ScopedAccess>> nodeAccesses = EconomicMap.create();
853871
final ResolvedJavaType memorySessionType = context.getMetaAccess().lookupJavaType(MemorySessionImpl.class);
854872
assert memorySessionType != null;
@@ -909,7 +927,7 @@ public void run() {
909927

910928
private void processNode(FixedNode f) {
911929
if (!scopes.isEmpty() && f instanceof Invoke i) {
912-
if (i.getTargetMethod() != null && calleeMightUseArena(i.getTargetMethod())) {
930+
if (i.getTargetMethod() != null && !config.isSafeCallee(i.getTargetMethod())) {
913931
if (!defs.isEmpty()) {
914932
dominatedCalls.add(new DominatedCall(defs.peek().defNode, i));
915933
}
@@ -974,21 +992,6 @@ private void cacheSafepointPosition(ReachingDefScope existingDef, ValueNode scop
974992
existingAccesses.add(new ScopedSafepoint(scopeAssociatedVal, existingDef.defNode));
975993
}
976994

977-
/**
978-
* Special methods known to never access a memory arena. Normally part of the path that
979-
* checks the `checkValidStateRaw` and throws an exception otherwise.
980-
*/
981-
private boolean calleeMightUseArena(ResolvedJavaMethod targetMethod) {
982-
if (Uninterruptible.Utils.isUninterruptible(targetMethod)) {
983-
// Uninterruptible can never safepoint
984-
return false;
985-
}
986-
if (ForeignFunctionsFeature.singleton().getNeverAccessesSharedArena().contains(((HostedType) targetMethod.getDeclaringClass()).getWrapped())) {
987-
return false;
988-
}
989-
return !ForeignFunctionsFeature.singleton().getNeverAccessesSharedArenaMethods().contains(((HostedMethod) targetMethod).getWrapped());
990-
}
991-
992995
private static boolean visitInputsUntil(Node key, Node start) {
993996
NodeStack toProcess = new NodeStack();
994997
toProcess.push(start);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import com.oracle.svm.core.c.libc.LibCBase;
5454
import com.oracle.svm.core.c.libc.MuslLibC;
5555
import com.oracle.svm.core.config.ConfigurationValues;
56-
import com.oracle.svm.core.graal.RuntimeCompilation;
5756
import com.oracle.svm.core.heap.ReferenceHandler;
5857
import com.oracle.svm.core.jdk.VectorAPIEnabled;
5958
import com.oracle.svm.core.option.APIOption;
@@ -1482,16 +1481,24 @@ public static boolean isForeignAPIEnabled() {
14821481
@Option(help = "Enable support for Arena.ofShared ", type = Expert)//
14831482
public static final HostedOptionKey<Boolean> SharedArenaSupport = new HostedOptionKey<>(false, key -> {
14841483
if (key.getValue()) {
1485-
// GR-65268: Shared arenas cannot be used together with runtime compilations
1486-
UserError.guarantee(!RuntimeCompilation.isEnabled(), "Arena.ofShared is not supported with runtime compilations. " +
1487-
"Replace usages of Arena.ofShared with Arena.ofAuto and disable shared arena support.");
1484+
UserError.guarantee(isForeignAPIEnabled(), "Support for Arena.ofShared is only available with foreign API support. " +
1485+
"Enable foreign API support with %s",
1486+
SubstrateOptionsParser.commandArgument(ForeignAPISupport, "+"));
1487+
14881488
// GR-65162: Shared arenas cannot be used together with Vector API support
14891489
UserError.guarantee(!VectorAPIEnabled.getValue(), "Support for Arena.ofShared is not available with Vector API support. " +
14901490
"Either disable Vector API support using %s or replace usages of Arena.ofShared with Arena.ofAuto and disable shared arena support.",
14911491
SubstrateOptionsParser.commandArgument(VectorAPISupport, "-"));
14921492
}
14931493
});
14941494

1495+
@Fold
1496+
public static boolean isSharedArenaSupportEnabled() {
1497+
// GR-65162: Shared arenas cannot be used together with Vector API support
1498+
return isForeignAPIEnabled() && SubstrateOptions.SharedArenaSupport.getValue() &&
1499+
(SubstrateOptions.SharedArenaSupport.hasBeenSet() || !VectorAPIEnabled.getValue());
1500+
}
1501+
14951502
@Option(help = "Assume new types cannot be added after analysis", type = OptionType.Expert) //
14961503
public static final HostedOptionKey<Boolean> ClosedTypeWorld = new HostedOptionKey<>(true) {
14971504
@Override

0 commit comments

Comments
 (0)