diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignAPIPredicates.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignAPIPredicates.java index 65d9537d4895..7ee48acee9a5 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignAPIPredicates.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignAPIPredicates.java @@ -62,15 +62,24 @@ public boolean getAsBoolean() { } } - @Platforms(Platform.HOSTED_ONLY.class) public static final class SharedArenasEnabled implements BooleanSupplier { - public static boolean getValue() { - return SubstrateOptions.isForeignAPIEnabled() && SubstrateOptions.SharedArenaSupport.getValue(); + private static final String VECTOR_API_SUPPORT_OPTION_NAME = SubstrateOptionsParser.commandArgument(SubstrateOptions.VectorAPISupport, "-"); + private static final String SHARED_ARENA_SUPPORT_OPTION_NAME = SubstrateOptionsParser.commandArgument(SubstrateOptions.SharedArenaSupport, "-"); + + @Platforms(Platform.HOSTED_ONLY.class) + SharedArenasEnabled() { } @Override public boolean getAsBoolean() { - return SharedArenasEnabled.getValue(); + return SubstrateOptions.isSharedArenaSupportEnabled(); + } + + public static RuntimeException vectorAPIUnsupported() { + assert !SubstrateOptions.isSharedArenaSupportEnabled(); + throw VMError.unsupportedFeature("Support for Arena.ofShared is not available if Vector API support is enabled." + + "Either disable Vector API support using " + VECTOR_API_SUPPORT_OPTION_NAME + + " or replace usages of Arena.ofShared with Arena.ofAuto and disable shared arena support using " + SHARED_ARENA_SUPPORT_OPTION_NAME + "."); } } @@ -83,11 +92,11 @@ public static final class SharedArenasDisabled implements BooleanSupplier { @Override public boolean getAsBoolean() { - return !SharedArenasEnabled.getValue(); + return !SubstrateOptions.isSharedArenaSupportEnabled(); } public static RuntimeException fail() { - assert !SharedArenasEnabled.getValue(); + assert !SubstrateOptions.isSharedArenaSupportEnabled(); throw VMError.unsupportedFeature("Support for Arena.ofShared is not active: enable with " + SHARED_ARENA_SUPPORT_OPTION_NAME); } } diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java index 6ed9f84d0b7c..c9b65dc18c36 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java @@ -36,8 +36,8 @@ import java.util.Map; import java.util.function.BiConsumer; -import com.oracle.svm.core.util.ImageHeapMap; import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.EconomicSet; import org.graalvm.collections.Pair; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -54,12 +54,14 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.InvokeJavaFunctionPointer; +import com.oracle.svm.core.foreign.phases.SubstrateOptimizeSharedArenaAccessPhase.OptimizeSharedArenaConfig; import com.oracle.svm.core.headers.LibC; import com.oracle.svm.core.headers.WindowsAPIs; import com.oracle.svm.core.image.DisallowedImageHeapObjects.DisallowedObjectReporter; import com.oracle.svm.core.snippets.SnippetRuntime; import com.oracle.svm.core.snippets.SubstrateForeignCallTarget; import com.oracle.svm.core.util.BasedOnJDKFile; +import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.api.replacements.Fold; @@ -68,8 +70,10 @@ import jdk.internal.foreign.MemorySessionImpl; import jdk.internal.foreign.abi.CapturableState; import jdk.internal.foreign.abi.LinkerOptions; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; -public class ForeignFunctionsRuntime implements ForeignSupport { +public class ForeignFunctionsRuntime implements ForeignSupport, OptimizeSharedArenaConfig { @Fold public static ForeignFunctionsRuntime singleton() { return ImageSingletons.lookup(ForeignFunctionsRuntime.class); @@ -80,6 +84,8 @@ public static ForeignFunctionsRuntime singleton() { private final EconomicMap downcallStubs = ImageHeapMap.create("downcallStubs"); private final EconomicMap, FunctionPointerHolder> directUpcallStubs = ImageHeapMap.create("directUpcallStubs"); private final EconomicMap upcallStubs = ImageHeapMap.create("upcallStubs"); + private final EconomicSet neverAccessesSharedArenaTypes = EconomicSet.create(); + private final EconomicSet neverAccessesSharedArenaMethods = EconomicSet.create(); private final Map trampolines = new HashMap<>(); private TrampolineSet currentTrampolineSet; @@ -151,6 +157,16 @@ public boolean addDirectUpcallStubPointer(DirectMethodHandleDesc desc, JavaEntry return directUpcallStubs.putIfAbsent(key, new FunctionPointerHolder(ptr)) == null; } + @Platforms(Platform.HOSTED_ONLY.class) + public void registerSafeArenaAccessorClass(ResolvedJavaType type) { + neverAccessesSharedArenaTypes.add(type); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void registerSafeArenaAccessorMethod(ResolvedJavaMethod method) { + neverAccessesSharedArenaMethods.add(method); + } + /** * We'd rather report the function descriptor than the native method type, but we don't have it * available here. One could intercept this exception in @@ -368,6 +384,17 @@ public static void captureCallState(int statesToCapture, CIntPointer captureBuff @Platforms(Platform.HOSTED_ONLY.class)// public static final SnippetRuntime.SubstrateForeignCallDescriptor CAPTURE_CALL_STATE = SnippetRuntime.findForeignCall(ForeignFunctionsRuntime.class, "captureCallState", HAS_SIDE_EFFECT, LocationIdentity.any()); + + @Override + public boolean isSafeCallee(ResolvedJavaMethod method) { + if (neverAccessesSharedArenaMethods.contains(method)) { + return true; + } + if (neverAccessesSharedArenaTypes.contains(method.getDeclaringClass())) { + return true; + } + return false; + } } interface StubPointer extends CFunctionPointer { diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/SubstrateForeignUtil.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/SubstrateForeignUtil.java index 38d7fe7150bb..960442874d7e 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/SubstrateForeignUtil.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/SubstrateForeignUtil.java @@ -31,6 +31,7 @@ import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ExceptionInputNode; import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ExceptionPathNode; import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.RegularPathNode; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.LogUtils; import jdk.graal.compiler.api.directives.GraalDirectives; @@ -69,6 +70,24 @@ public static void checkSession(MemorySessionImpl session) { } } + /** + * A decorator method for {@link MemorySessionImpl#checkValidStateRaw()} which will fail if a + * shared session is passed. We use this method instead of the decorated one in runtime + * compiled @Scoped-annotated methods (and their deopt targets) to fail if shared sessions are + * used in runtime compiled methods (GR-66841). + */ + @AlwaysInline("factored out only for readability") + public static void checkValidStateRawInRuntimeCompiledCode(MemorySessionImpl session) { + /* + * A closeable session without an owner thread is a shared session (i.e. it can be closed + * concurrently). + */ + if (session.isCloseable() && session.ownerThread() == null) { + throw VMError.unsupportedFeature("Invalid session object. Using a shared session in runtime compiled methods is not supported."); + } + session.checkValidStateRaw(); + } + /** * Handles exceptions related to memory sessions within a specific arena scope. * diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java index 911f9e50456d..fd1a248a1bf8 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java @@ -39,9 +39,11 @@ import com.oracle.svm.core.util.BasedOnJDKFile; import jdk.internal.access.foreign.MappedMemoryUtilsProxy; +import jdk.internal.foreign.AbstractMemorySegmentImpl; import jdk.internal.foreign.MemorySessionImpl; import jdk.internal.misc.ScopedMemoryAccess; import jdk.internal.misc.ScopedMemoryAccess.ScopedAccessError; +import jdk.internal.vm.vector.VectorSupport; /** * Support for shared arenas on SVM: @@ -184,6 +186,53 @@ public void forceInternal(MemorySessionImpl session, MappedMemoryUtilsProxy mapp } } + @SuppressWarnings("unused") + @Substitute + @TargetElement(onlyWith = SharedArenasEnabled.class) + @AlwaysInline("Safepoints must be visible in caller") + private static , E, S extends VectorSupport.VectorSpecies> V loadFromMemorySegmentScopedInternal(MemorySessionImpl session, + Class vmClass, Class e, int length, + AbstractMemorySegmentImpl msp, long offset, + S s, + VectorSupport.LoadOperation defaultImpl) { + throw SharedArenasEnabled.vectorAPIUnsupported(); + } + + @SuppressWarnings("unused") + @Substitute + @TargetElement(onlyWith = SharedArenasEnabled.class) + @AlwaysInline("Safepoints must be visible in caller") + private static , E, S extends VectorSupport.VectorSpecies, M extends VectorSupport.VectorMask> V loadFromMemorySegmentMaskedScopedInternal( + MemorySessionImpl session, Class vmClass, + Class maskClass, Class e, int length, + AbstractMemorySegmentImpl msp, long offset, M m, + S s, int offsetInRange, + VectorSupport.LoadVectorMaskedOperation defaultImpl) { + throw SharedArenasEnabled.vectorAPIUnsupported(); + } + + @SuppressWarnings("unused") + @Substitute + @TargetElement(onlyWith = SharedArenasEnabled.class) + @AlwaysInline("Safepoints must be visible in caller") + public static , E> void storeIntoMemorySegment(Class vmClass, Class e, int length, + V v, + AbstractMemorySegmentImpl msp, long offset, + VectorSupport.StoreVectorOperation defaultImpl) { + throw SharedArenasEnabled.vectorAPIUnsupported(); + } + + @SuppressWarnings("unused") + @Substitute + @TargetElement(onlyWith = SharedArenasEnabled.class) + @AlwaysInline("Safepoints must be visible in caller") + public static , E, M extends VectorSupport.VectorMask> void storeIntoMemorySegmentMasked(Class vmClass, Class maskClass, Class e, + int length, V v, M m, + AbstractMemorySegmentImpl msp, long offset, + VectorSupport.StoreVectorMaskedOperation defaultImpl) { + throw SharedArenasEnabled.vectorAPIUnsupported(); + } + /** * This method synchronizes with all other Java threads in order to be able to safely close the * session. diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/phases/SubstrateOptimizeSharedArenaAccessPhase.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/phases/SubstrateOptimizeSharedArenaAccessPhase.java similarity index 97% rename from substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/phases/SubstrateOptimizeSharedArenaAccessPhase.java rename to substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/phases/SubstrateOptimizeSharedArenaAccessPhase.java index 37cef558e265..af07cf1713c6 100644 --- a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/phases/SubstrateOptimizeSharedArenaAccessPhase.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/phases/SubstrateOptimizeSharedArenaAccessPhase.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted.foreign.phases; +package com.oracle.svm.core.foreign.phases; import static jdk.graal.compiler.debug.DebugContext.VERY_DETAILED_LEVEL; @@ -39,7 +39,6 @@ import org.graalvm.collections.Equivalence; import org.graalvm.word.LocationIdentity; -import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.nodes.ClusterNode; import com.oracle.svm.core.nodes.foreign.MemoryArenaValidInScopeNode; import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ClusterBeginNode; @@ -47,9 +46,6 @@ import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ExceptionPathNode; import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.RegularPathNode; import com.oracle.svm.core.nodes.foreign.ScopedMethodNode; -import com.oracle.svm.hosted.foreign.ForeignFunctionsFeature; -import com.oracle.svm.hosted.meta.HostedMethod; -import com.oracle.svm.hosted.meta.HostedType; import jdk.graal.compiler.core.common.cfg.CFGLoop; import jdk.graal.compiler.debug.Assertions; @@ -346,10 +342,24 @@ */ public class SubstrateOptimizeSharedArenaAccessPhase extends BasePhase implements RecursivePhase { + public interface OptimizeSharedArenaConfig { + /** + * Tests if calls to the given callee may remain in the critical region of + * an @Scoped-annotated method. Usually, this is the case if (1) the callee does not do any + * safepoint checks (recursively), or (2) the callee does not access native memory of a + * shared arena AND exits with an exception. The second case normally covers methods that + * are part of the path that checks the 'checkValidStateRaw' and throws an exception + * otherwise. + */ + boolean isSafeCallee(ResolvedJavaMethod method); + } + final CanonicalizerPhase canonicalizer; + final OptimizeSharedArenaConfig config; - public SubstrateOptimizeSharedArenaAccessPhase(CanonicalizerPhase canonicalizer) { + public SubstrateOptimizeSharedArenaAccessPhase(CanonicalizerPhase canonicalizer, OptimizeSharedArenaConfig config) { this.canonicalizer = canonicalizer; + this.config = config; } @Override @@ -638,13 +648,20 @@ protected void run(StructuredGraph graph, MidTierContext context) { cleanupClusterNodes(graph, context, insertSessionChecks(graph, context)); } - private static EconomicSet insertSessionChecks(StructuredGraph graph, MidTierContext context) { + private EconomicSet insertSessionChecks(StructuredGraph graph, MidTierContext context) { + if (graph.getNodes().filter(ScopedMethodNode.class).count() == 0) { + /* + * We are, for whatever reason, compiling the exception handler template method, we do + * not verify no calls and we do not duplicate any session checks inside. + */ + return null; + } ControlFlowGraph cfg = ControlFlowGraph.newBuilder(graph).modifiableBlocks(true).connectBlocks(true).computeFrequency(true).computeLoops(true).computeDominators(true) .computePostdominators(true) .build(); // Compute the graph with all the necessary data about scoped memory accesses. EconomicSet calls = EconomicSet.create(); - EconomicMap> sugaredGraph = enumerateScopedAccesses(cfg, context, calls); + EconomicMap> sugaredGraph = enumerateScopedAccesses(config, cfg, context, calls); if (sugaredGraph != null) { ReentrantBlockIterator.apply(new MinimalSessionChecks(graph, sugaredGraph, cfg, calls), cfg.getStartBlock()); } @@ -848,7 +865,8 @@ record DominatedCall(MemoryArenaValidInScopeNode defNode, Invoke invoke) { } - private static EconomicMap> enumerateScopedAccesses(ControlFlowGraph cfg, MidTierContext context, EconomicSet dominatedCalls) { + private static EconomicMap> enumerateScopedAccesses(OptimizeSharedArenaConfig config, ControlFlowGraph cfg, MidTierContext context, + EconomicSet dominatedCalls) { EconomicMap> nodeAccesses = EconomicMap.create(); final ResolvedJavaType memorySessionType = context.getMetaAccess().lookupJavaType(MemorySessionImpl.class); assert memorySessionType != null; @@ -909,7 +927,7 @@ public void run() { private void processNode(FixedNode f) { if (!scopes.isEmpty() && f instanceof Invoke i) { - if (i.getTargetMethod() != null && calleeMightUseArena(i.getTargetMethod())) { + if (i.getTargetMethod() != null && !config.isSafeCallee(i.getTargetMethod())) { if (!defs.isEmpty()) { dominatedCalls.add(new DominatedCall(defs.peek().defNode, i)); } @@ -974,21 +992,6 @@ private void cacheSafepointPosition(ReachingDefScope existingDef, ValueNode scop existingAccesses.add(new ScopedSafepoint(scopeAssociatedVal, existingDef.defNode)); } - /** - * Special methods known to never access a memory arena. Normally part of the path that - * checks the `checkValidStateRaw` and throws an exception otherwise. - */ - private boolean calleeMightUseArena(ResolvedJavaMethod targetMethod) { - if (Uninterruptible.Utils.isUninterruptible(targetMethod)) { - // Uninterruptible can never safepoint - return false; - } - if (ForeignFunctionsFeature.singleton().getNeverAccessesSharedArena().contains(((HostedType) targetMethod.getDeclaringClass()).getWrapped())) { - return false; - } - return !ForeignFunctionsFeature.singleton().getNeverAccessesSharedArenaMethods().contains(((HostedMethod) targetMethod).getWrapped()); - } - private static boolean visitInputsUntil(Node key, Node start) { NodeStack toProcess = new NodeStack(); toProcess.push(start); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 6ba30d001be5..934259f4c1c2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -53,7 +53,6 @@ import com.oracle.svm.core.c.libc.LibCBase; import com.oracle.svm.core.c.libc.MuslLibC; import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.graal.RuntimeCompilation; import com.oracle.svm.core.heap.ReferenceHandler; import com.oracle.svm.core.jdk.VectorAPIEnabled; import com.oracle.svm.core.option.APIOption; @@ -1478,9 +1477,10 @@ public static boolean isForeignAPIEnabled() { @Option(help = "Enable support for Arena.ofShared ", type = Expert)// public static final HostedOptionKey SharedArenaSupport = new HostedOptionKey<>(false, key -> { if (key.getValue()) { - // GR-65268: Shared arenas cannot be used together with runtime compilations - UserError.guarantee(!RuntimeCompilation.isEnabled(), "Arena.ofShared is not supported with runtime compilations. " + - "Replace usages of Arena.ofShared with Arena.ofAuto and disable shared arena support."); + UserError.guarantee(isForeignAPIEnabled(), "Support for Arena.ofShared is only available with foreign API support. " + + "Enable foreign API support with %s", + SubstrateOptionsParser.commandArgument(ForeignAPISupport, "+")); + // GR-65162: Shared arenas cannot be used together with Vector API support UserError.guarantee(!VectorAPIEnabled.getValue(), "Support for Arena.ofShared is not available with Vector API support. " + "Either disable Vector API support using %s or replace usages of Arena.ofShared with Arena.ofAuto and disable shared arena support.", @@ -1488,6 +1488,13 @@ public static boolean isForeignAPIEnabled() { } }); + @Fold + public static boolean isSharedArenaSupportEnabled() { + // GR-65162: Shared arenas cannot be used together with Vector API support + return isForeignAPIEnabled() && SubstrateOptions.SharedArenaSupport.getValue() && + (SubstrateOptions.SharedArenaSupport.hasBeenSet() || !VectorAPIEnabled.getValue()); + } + @Option(help = "Assume new types cannot be added after analysis", type = OptionType.Expert) // public static final HostedOptionKey ClosedTypeWorld = new HostedOptionKey<>(true) { @Override diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java index 9b63de13feaf..20297abc18d6 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java @@ -104,6 +104,7 @@ import com.oracle.svm.hosted.ProgressReporter; import com.oracle.svm.hosted.RuntimeCompilationCallbacks; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.SharedArenaSupport; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.analysis.SVMParsingSupport; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; @@ -475,6 +476,13 @@ public void beforeAnalysis(BeforeAnalysisAccess c) { for (ResolvedJavaMethod method : replacements.getSnippetMethods()) { objectReplacer.apply(method); } + + /* + * Register the same allow list as for hosted compiles also for runtime compiles. + */ + if (SharedArenaSupport.isAvailable()) { + SharedArenaSupport.singleton().registerSafeArenaAccessorsForRuntimeCompilation(objectReplacer::createMethod, objectReplacer::createType); + } } boolean newRuntimeMethodsSeen = false; diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java index 93364b3ec3ca..b13ba343e97b 100644 --- a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java +++ b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java @@ -44,10 +44,12 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import java.util.function.Supplier; import java.util.function.UnaryOperator; import org.graalvm.collections.EconomicSet; +import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -57,33 +59,41 @@ import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeForeignAccessSupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.svm.common.meta.MultiMethod; import com.oracle.svm.configure.ConfigurationFile; import com.oracle.svm.configure.ConfigurationParser; import com.oracle.svm.core.ForeignSupport; +import com.oracle.svm.core.JavaMemoryUtil; import com.oracle.svm.core.OS; +import com.oracle.svm.core.ParsingReason; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.code.FactoryMethodHolder; import com.oracle.svm.core.code.FactoryThrowMethodHolder; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.foreign.AbiUtils; -import com.oracle.svm.core.foreign.ForeignAPIPredicates; import com.oracle.svm.core.foreign.ForeignFunctionsRuntime; import com.oracle.svm.core.foreign.JavaEntryPointInfo; import com.oracle.svm.core.foreign.NativeEntryPointInfo; import com.oracle.svm.core.foreign.RuntimeSystemLookup; +import com.oracle.svm.core.foreign.SubstrateForeignUtil; import com.oracle.svm.core.foreign.SubstrateMappedMemoryUtils; import com.oracle.svm.core.foreign.Target_java_nio_MappedMemoryUtils; +import com.oracle.svm.core.foreign.phases.SubstrateOptimizeSharedArenaAccessPhase; import com.oracle.svm.core.graal.RuntimeCompilation; import com.oracle.svm.core.graal.meta.SubstrateForeignCallsProvider; import com.oracle.svm.core.jdk.VectorAPIEnabled; import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.nodes.SubstrateMethodCallTargetNode; import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; @@ -95,18 +105,26 @@ import com.oracle.svm.hosted.SharedArenaSupport; import com.oracle.svm.hosted.code.CEntryPointData; import com.oracle.svm.hosted.config.ConfigurationParserUtils; -import com.oracle.svm.hosted.foreign.phases.SubstrateOptimizeSharedArenaAccessPhase; +import com.oracle.svm.hosted.meta.HostedMethod; +import com.oracle.svm.hosted.meta.HostedType; import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.nodes.CallTargetNode.InvokeKind; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; +import jdk.graal.compiler.nodes.graphbuilderconf.NodePlugin; +import jdk.graal.compiler.nodes.java.MethodCallTargetNode; import jdk.graal.compiler.phases.BasePhase; import jdk.graal.compiler.phases.PhaseSuite; import jdk.graal.compiler.phases.common.CanonicalizerPhase; import jdk.graal.compiler.phases.common.IterativeConditionalEliminationPhase; import jdk.graal.compiler.phases.tiers.MidTierContext; +import jdk.graal.compiler.phases.util.Providers; import jdk.internal.foreign.AbstractMemorySegmentImpl; import jdk.internal.foreign.MemorySessionImpl; import jdk.internal.foreign.abi.AbstractLinker; @@ -303,13 +321,21 @@ private void createStub(StubFactory createOptimizeSharedArenaAccessPhase() { - VMError.guarantee(ForeignAPIPredicates.SharedArenasEnabled.getValue(), "Support for shared arenas must be enabled"); - VMError.guarantee(!RuntimeCompilation.isEnabled(), "Shared arenas cannot be used together with runtime compilations (GR-65268)"); + public BasePhase createOptimizeSharedArenaAccessPhase(boolean hosted) { + VMError.guarantee(SubstrateOptions.isSharedArenaSupportEnabled(), "Support for shared arenas must be enabled"); VMError.guarantee(!VectorAPIEnabled.getValue(), "Shared arenas cannot be used together with Vector API support (GR-65162)"); PhaseSuite sharedArenaPhases = new PhaseSuite<>(); - sharedArenaPhases.appendPhase(new SubstrateOptimizeSharedArenaAccessPhase(CanonicalizerPhase.create())); + if (hosted) { + sharedArenaPhases.appendPhase(new SubstrateOptimizeSharedArenaAccessPhase(CanonicalizerPhase.create(), method -> { + if (getNeverAccessesSharedArena().contains(((HostedType) method.getDeclaringClass()).getWrapped())) { + return true; + } + return getNeverAccessesSharedArenaMethods().contains(((HostedMethod) method).getWrapped()); + })); + } else { + sharedArenaPhases.appendPhase(new SubstrateOptimizeSharedArenaAccessPhase(CanonicalizerPhase.create(), foreignFunctionsRuntime)); + } /* * After we injected all necessary scope wide session checks we need to cleanup any new, * potentially repetitive, control flow logic. @@ -321,9 +347,19 @@ public BasePhase createOptimizeSharedArenaAccessPhase() { @Override public void registerSafeArenaAccessorClass(AnalysisMetaAccess metaAccess, Class klass) { - assert ForeignAPIPredicates.SharedArenasEnabled.getValue(); + assert SubstrateOptions.isSharedArenaSupportEnabled(); ForeignFunctionsFeature.this.registerSafeArenaAccessorClass(metaAccess, klass); } + + @Override + public void registerSafeArenaAccessorsForRuntimeCompilation(Function objectReplacer, Function createType) { + for (var method : getNeverAccessesSharedArenaMethods()) { + foreignFunctionsRuntime.registerSafeArenaAccessorMethod(objectReplacer.apply(method)); + } + for (var type : getNeverAccessesSharedArena()) { + foreignFunctionsRuntime.registerSafeArenaAccessorClass(createType.apply(type)); + } + } } protected ForeignFunctionsFeature() { @@ -360,8 +396,7 @@ public void duringSetup(DuringSetupAccess a) { var access = (FeatureImpl.DuringSetupAccessImpl) a; accessSupport = new RuntimeForeignAccessSupportImpl(access.getMetaAccess(), access.getUniverse()); ImageSingletons.add(RuntimeForeignAccessSupport.class, accessSupport); - if (SubstrateOptions.SharedArenaSupport.getValue()) { - assert ForeignAPIPredicates.SharedArenasEnabled.getValue(); + if (SubstrateOptions.isSharedArenaSupportEnabled()) { ImageSingletons.add(SharedArenaSupport.class, new SharedArenaSupportImpl()); } @@ -700,10 +735,22 @@ protected void initSafeArenaAccessors(BeforeAnalysisAccessImpl access) throws No * checkValidStateRaw */ registerSafeArenaAccessorMethod(metaAccess, Supplier.class.getMethod("get")); - registerSafeArenaAccessorMethod(metaAccess, VMError.class.getMethod("shouldNotReachHereSubstitution")); registerSafeArenaAccessorMethod(metaAccess, ScopedAccessError.class.getMethod("newRuntimeException")); registerSafeArenaAccessorMethod(metaAccess, Throwable.class.getMethod("getMessage")); registerSafeArenaAccessorMethod(metaAccess, ReflectionUtil.lookupMethod(Throwable.class, "fillInStackTrace", int.class)); + registerSafeArenaAccessorClass(metaAccess, VMError.class); + + /* + * Our uninterruptible implementations of Unsafe.setMemory0, Unsafe.copyMemory0, and + * Unsafe.copySwapMemory0 are also safe to be called. + */ + registerSafeArenaAccessorMethod(metaAccess, + ReflectionUtil.lookupMethod(JavaMemoryUtil.class, "copyOnHeap", Object.class, UnsignedWord.class, Object.class, UnsignedWord.class, UnsignedWord.class)); + registerSafeArenaAccessorMethod(metaAccess, ReflectionUtil.lookupMethod(JavaMemoryUtil.class, "fill", Pointer.class, UnsignedWord.class, byte.class)); + registerSafeArenaAccessorMethod(metaAccess, ReflectionUtil.lookupMethod(JavaMemoryUtil.class, "fillOnHeap", Object.class, long.class, long.class, byte.class)); + registerSafeArenaAccessorMethod(metaAccess, ReflectionUtil.lookupMethod(JavaMemoryUtil.class, "copySwapOnHeap", Object.class, long.class, Object.class, long.class, long.class, long.class)); + registerSafeArenaAccessorMethod(metaAccess, ReflectionUtil.lookupMethod(JavaMemoryUtil.class, "copySwap", Pointer.class, Pointer.class, UnsignedWord.class, UnsignedWord.class)); + registerSafeArenaAccessorMethod(metaAccess, ReflectionUtil.lookupMethod(UnmanagedMemoryUtil.class, "copy", Pointer.class, Pointer.class, UnsignedWord.class)); /* * Calls to the following methods may remain in the @Scoped-annotated methods because they @@ -716,6 +763,7 @@ protected void initSafeArenaAccessors(BeforeAnalysisAccessImpl access) throws No registerSafeArenaAccessorMethod(metaAccess, ReflectionUtil.lookupMethod(mappedMemoryUtils, "isLoaded", long.class, boolean.class, long.class)); registerSafeArenaAccessorMethod(metaAccess, ReflectionUtil.lookupMethod(mappedMemoryUtils, "unload", long.class, boolean.class, long.class)); registerSafeArenaAccessorMethod(metaAccess, ReflectionUtil.lookupMethod(SubstrateMappedMemoryUtils.class, "load", long.class, boolean.class, long.class)); + registerSafeArenaAccessorMethod(metaAccess, Thread.class.getMethod("currentThread")); /* * The actual method checking a valid session state (if not inlined) is also safe as this @@ -753,6 +801,42 @@ public void registerForeignCalls(SubstrateForeignCallsProvider foreignCalls) { foreignCalls.register(ForeignFunctionsRuntime.CAPTURE_CALL_STATE); } + @Override + public void registerGraphBuilderPlugins(Providers providers, Plugins plugins, ParsingReason reason) { + /* + * If support for shared arenas is enabled, register a graph builder plugin that replaces + * invocations '((MemorySessionImpl)session).checkValidStateRaw' with + * 'SubstrateForeignUtil.checkValidStateRawInRuntimeCompiledCode(session)' + * in @Scoped-annotated methods that are built for runtime compilation (GR-66841). We use a + * graph builder plugin such that the invocation can already be replaced during bytecode + * parsing where the call is still virtual (and thus, an invocation plugin won't trigger). + */ + if (!SubstrateOptions.isSharedArenaSupportEnabled() || !RuntimeCompilation.isEnabled()) { + return; + } + + ResolvedJavaMethod checkValidState = providers.getMetaAccess().lookupJavaMethod(ReflectionUtil.lookupMethod(MemorySessionImpl.class, "checkValidStateRaw")); + ResolvedJavaMethod checkValidStateRawInRuntimeCompiledCode = providers.getMetaAccess().lookupJavaMethod( + ReflectionUtil.lookupMethod(SubstrateForeignUtil.class, "checkValidStateRawInRuntimeCompiledCode", MemorySessionImpl.class)); + plugins.appendNodePlugin(new NodePlugin() { + @Override + public boolean handleInvoke(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args) { + if (!checkValidState.equals(method)) { + return false; + } + if (MultiMethod.isOriginalMethod(b.getMethod())) { // not for hosted compilation + return false; + } + if (!AnnotationAccess.isAnnotationPresent(b.getMethod(), SharedArenaSupport.SCOPED_ANNOTATION)) { + return false; + } + MethodCallTargetNode mt = b.add(new SubstrateMethodCallTargetNode(InvokeKind.Static, checkValidStateRawInRuntimeCompiledCode, args, b.getInvokeReturnStamp(b.getAssumptions()))); + b.handleReplacedInvoke(mt, b.getInvokeReturnType().getJavaKind()); + return true; + } + }); + } + /* Testing and reporting interface */ public int getCreatedDowncallStubsCount() { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index b9a1e7c10006..4156a15ebb4e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -1594,9 +1594,9 @@ private static Suites modifySuites(SubstrateBackend backend, Suites suites, Feat lowTier.replacePlaceholder(AddressLoweringPhase.class, addressLoweringPhase); lowTier.replacePlaceholder(TransplantGraphsPhase.class, new TransplantGraphsPhase(createSuitesForLateSnippetTemplate(suites))); - if (hosted && SharedArenaSupport.isAvailable()) { + if (SharedArenaSupport.isAvailable()) { var pos = midTier.findPhase(FrameStateAssignmentPhase.class, true); - pos.add(SharedArenaSupport.singleton().createOptimizeSharedArenaAccessPhase()); + pos.add(SharedArenaSupport.singleton().createOptimizeSharedArenaAccessPhase(hosted)); } /* diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SharedArenaSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SharedArenaSupport.java index 41105b127a1c..b03e2a81d000 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SharedArenaSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SharedArenaSupport.java @@ -25,6 +25,7 @@ package com.oracle.svm.hosted; import java.lang.annotation.Annotation; +import java.util.function.Function; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.ImageSingletons; @@ -37,6 +38,7 @@ import jdk.graal.compiler.phases.BasePhase; import jdk.graal.compiler.phases.tiers.MidTierContext; import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; public interface SharedArenaSupport { @@ -54,10 +56,12 @@ static SharedArenaSupport singleton() { return ImageSingletons.lookup(SharedArenaSupport.class); } - BasePhase createOptimizeSharedArenaAccessPhase(); + BasePhase createOptimizeSharedArenaAccessPhase(boolean hosted); void registerSafeArenaAccessorClass(AnalysisMetaAccess metaAccess, Class klass); + void registerSafeArenaAccessorsForRuntimeCompilation(Function createMethod, Function createType); + static boolean isScopedMethod(ResolvedJavaMethod method) { ResolvedJavaMethod originalMethod = OriginalMethodProvider.getOriginalMethod(method); return originalMethod != null && AnnotationAccess.isAnnotationPresent(originalMethod, SCOPED_ANNOTATION); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/DeoptimizationUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/DeoptimizationUtils.java index 367423ff82fb..e2f6e38b3f48 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/DeoptimizationUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/DeoptimizationUtils.java @@ -304,7 +304,7 @@ static boolean verifyDeoptTarget(HostedMethod method, StructuredGraph graph, Com long encodedBci = FrameInfoEncoder.encodeBci(frame.getBCI(), FrameState.StackState.of(frame)); BytecodeFrame previous = encodedBciMap.put(encodedBci, frame); - assert previous == null : "duplicate encoded bci " + encodedBci + " in deopt target " + method + " found.\n\n" + frame + + assert previous == null : "duplicate encoded bci " + encodedBci + " (original=" + frame.getBCI() + ") in deopt target " + method + " found.\n\n" + frame + "\n\n" + previous; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java index 8140bc69ec02..20c57a42fea1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java @@ -321,7 +321,16 @@ public boolean inAnyInliningScope() { public AccumulativeInlineScope createAccumulativeInlineScope(AccumulativeInlineScope outer, AnalysisMethod caller, AnalysisMethod method, NodePredicate invalidNodePredicate) { AccumulativeCounters accumulativeCounters; int depth; - if (outer == null) { + if (isScopedMethod(caller)) { + /* + * Inlining into @Scope-annotated methods is required for correctness since in general, + * no calls may remain. Therefore, regardless if there is already an outer scope, those + * methods are always treated as inlining root. + */ + depth = 1; + accumulativeCounters = new AccumulativeCounters(optionScopedAllowedNodes, optionScopedAllowedInvokes, InliningScopeType.ScopedMethod); + + } else if (outer == null) { /* * The first level of method inlining, i.e., the top scope from the inlining policy * point of view. @@ -339,9 +348,6 @@ public AccumulativeInlineScope createAccumulativeInlineScope(AccumulativeInlineS } else if (optionTrackNeverNullInstanceFields && FactoryMethodSupport.isFactoryMethod(caller)) { accumulativeCounters = new AccumulativeCounters(optionConstructorAllowedNodes, optionConstructorAllowedInvokes, InliningScopeType.ConstructorInlining); - } else if (isScopedMethod(caller)) { - accumulativeCounters = new AccumulativeCounters(optionScopedAllowedNodes, optionScopedAllowedInvokes, InliningScopeType.ScopedMethod); - } else { accumulativeCounters = new AccumulativeCounters(optionAllowedNodes, optionAllowedInvokes, InliningScopeType.None); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java index 2e6b6ccb1314..7797617f2452 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java @@ -253,8 +253,23 @@ protected void build(FixedWithNextNode startInstruction, FrameStateBuilder start } } - if (graph.method() != null) { + if (!isMethodDeoptTarget() && graph.method() != null) { + /* + * A note on deoptimization, runtime compilation and shared arena support on svm: We + * instrument the runtime compiled versions of methods correctly. But instrumenting + * the deopt versions is hard because we cannot just create fake frame states (the + * frame state verification is very strict in this case) and we would need to + * generate appropriate bytecode. If a transition from the runtime compiled method + * to the deopt target happens, either a ScopedAccessError happened (i.e. the arena + * was closed) or the arena is still valid when initiating the deoptimization. + * Unfortunately, the deopt itself happens in a safepoint where other VM operations + * may be scheduled as well. Also, lazy deoptimization is interruptible. We can + * therefore not guarantee, that the session won't be closed during the transition + * to the deopt target. In order to solve this, we will need to insert session + * checks after each deopt entry in the deopt target (GR-66841). + */ try { + graph.getDebug().dump(DebugContext.VERY_DETAILED_LEVEL, graph, "Before instrumenting @Scoped method"); if (AnnotationAccess.isAnnotationPresent(method, ForeignSupport.Scoped.class) && SharedArenaSupport.isAvailable()) { // substituted, only add the scoped node introduceScopeNodes(); @@ -262,6 +277,7 @@ protected void build(FixedWithNextNode startInstruction, FrameStateBuilder start // not substituted, also instrument instrumentScopedMethod(); } + graph.getDebug().dump(DebugContext.VERY_DETAILED_LEVEL, graph, "After instrumenting @Scoped method"); } catch (Throwable e) { throw GraalError.shouldNotReachHere(e); } @@ -329,12 +345,31 @@ private void introduceScopeInstrumentationExceptionHandlers() { GraalError.guarantee(unwinds.size() == 1, "Exactly one unwind node expected."); final UnwindNode unwind = unwinds.get(0); - FrameState unwindMergeStateTemplate = null; - if (unwind.predecessor() instanceof MergeNode m) { - unwindMergeStateTemplate = m.stateAfter().duplicateWithVirtualState(); - } for (SessionCheck sessionCheck : sessionsToCheck) { + + Objects.requireNonNull(unwind); + Objects.requireNonNull(unwind.predecessor()); + + FrameState unwindMergeStateTemplate; + + FixedNode prevBegin = (FixedNode) unwind.predecessor(); + while (prevBegin instanceof BeginNode) { + prevBegin = (FixedNode) prevBegin.predecessor(); + } + if (prevBegin instanceof MergeNode m) { + unwindMergeStateTemplate = m.stateAfter().duplicateWithVirtualState(); + } else { + // try to see if we can walk back and find only an exception object node + if (prevBegin instanceof ExceptionObjectNode e) { + unwindMergeStateTemplate = e.stateAfter().duplicateWithVirtualState(); + } else { + throw GraalError.shouldNotReachHere("No merge predecessor found for " + unwind + " and prev begin " + prevBegin); + } + } + + GraalError.guarantee(unwindMergeStateTemplate != null, "Must have a state on the unwind predecessor but did not find any for %s", unwind); + graph.getDebug().dump(DebugContext.VERY_DETAILED_LEVEL, graph, "Before inserting exception handlers for scoped unwind paths"); assert sessionCheck.session != null : Assertions.errorMessage("At least the session must never be null", sessionsToCheck); ValueNode[] args = new ValueNode[]{sessionCheck.session, sessionCheck.base == null ? ConstantNode.defaultForKind(JavaKind.Object, graph) : sessionCheck.base, @@ -347,18 +382,12 @@ private void introduceScopeInstrumentationExceptionHandlers() { ExceptionObjectNode eon = graph.add(new ExceptionObjectNode(s)); GraalError.guarantee(eon.stamp(NodeView.DEFAULT) != null, "Must have a stamp %s", eon); - /* - * Build a fake state for the exception handler that is not existing in the - * bytecode. This is fine because we will never deopt here. Note that this is a real - * exception state with a bci, and not only one used for rethrowing. - */ eon.setStateAfter(graph.addOrUnique(new FrameState(0, eon, graph.start().stateAfter().getCode(), false))); /* a random bci 0, we are injecting an artificial call */ final int callBCI = 0; InvokeWithExceptionNode invoke = graph.add(new InvokeWithExceptionNode(mct, eon, callBCI)); invoke.setStateAfter(graph.start().stateAfter().duplicateWithVirtualState()); - invoke.stateAfter().invalidateForDeoptimization(); // hang the invoke in FixedNode afterStart = graph.start().next(); @@ -386,10 +415,7 @@ private void introduceScopeInstrumentationExceptionHandlers() { eonPhi.addInput(unwind.exception()); eonPhi.addInput(eon); - assert Objects.requireNonNull(unwindMergeStateTemplate).values().size() == 1 : Assertions.errorMessage("Exception path should only have exception object on stack", - unwindMergeStateTemplate); - - unwindMergeStateTemplate.replaceFirstInput(unwind.exception(), eonPhi); + unwindMergeStateTemplate.replaceAllInputs(unwind.exception(), eonPhi); newMergeBeforeUnwind.setStateAfter(unwindMergeStateTemplate); // duplicate for next occurrence diff --git a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java index 09338d88ab85..ac6449239695 100644 --- a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java +++ b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java @@ -79,6 +79,7 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.InvokeInfo; +import com.oracle.svm.core.UnsafeMemoryUtil; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; @@ -87,6 +88,7 @@ import com.oracle.svm.core.util.UserError; import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.SharedArenaSupport; import com.oracle.svm.hosted.config.ConfigurationParserUtils; import com.oracle.svm.util.ClassUtil; import com.oracle.truffle.api.TruffleLanguage; @@ -310,6 +312,16 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { }); accessImpl.getHostVM().keepAnalysisGraphs(); + + if (SharedArenaSupport.isAvailable()) { + /* + * Due to the enforced call boundary when entering a safe class, calls to methods of + * class UnsafeMemoryUtil may remain in @Scoped-annotated methods. Since this feature is + * only used for reporting and the resulting image never gets executed, we allow those + * calls to pass verification. + */ + SharedArenaSupport.singleton().registerSafeArenaAccessorClass(accessImpl.getMetaAccess(), UnsafeMemoryUtil.class); + } } private void initializeDeniedMethods(FeatureImpl.BeforeAnalysisAccessImpl accessImpl) {