From ba13327dad36cf3c93454d61830286ba0e354fed Mon Sep 17 00:00:00 2001 From: Sacha Coppey Date: Thu, 30 Oct 2025 14:53:25 +0100 Subject: [PATCH 1/2] Improve LayeredAnnotationProcessor to allow annotating any method --- .../LayeredCompilationBehavior.java | 2 +- .../processor/LayeredAnnotationProcessor.java | 54 +++++++++++++++---- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/layeredimage/LayeredCompilationBehavior.java b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/layeredimage/LayeredCompilationBehavior.java index 054fb1eca486..06b983b2fa4c 100644 --- a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/layeredimage/LayeredCompilationBehavior.java +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/layeredimage/LayeredCompilationBehavior.java @@ -33,7 +33,7 @@ * Used to specify how a method needs to be compiled when building layered images. */ @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD}) +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface LayeredCompilationBehavior { /** * This state represents how a method should be compiled in layered images. The state of a diff --git a/substratevm/src/com.oracle.svm.processor/src/com/oracle/svm/processor/LayeredAnnotationProcessor.java b/substratevm/src/com.oracle.svm.processor/src/com/oracle/svm/processor/LayeredAnnotationProcessor.java index 678b7bc4f4b9..5939a17a6826 100644 --- a/substratevm/src/com.oracle.svm.processor/src/com/oracle/svm/processor/LayeredAnnotationProcessor.java +++ b/substratevm/src/com.oracle.svm.processor/src/com/oracle/svm/processor/LayeredAnnotationProcessor.java @@ -38,6 +38,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import jdk.graal.compiler.processor.AbstractProcessor; @@ -56,12 +57,20 @@ public class LayeredAnnotationProcessor extends AbstractProcessor { static final String METHOD_NAME_COMPONENT_SEPARATOR = "__"; static final char OBJECT_PATH_SEPARATOR = '_'; static final String ARRAY_IDENTIFIER = "ARRAY_"; + static final String CONSTRUCTOR_NAME = ""; + static final String CLASS_INITIALIZER_NAME = ""; - private void processElement(ExecutableElement annotatedExecutable) { + private void processElement(ExecutableElement annotatedExecutable, AnnotationMirror compilationBehavior) { String featureClassName = computeFileName(annotatedExecutable); String packageName = getPackage(annotatedExecutable).getQualifiedName().toString(); + String methodName = computeMethodName(annotatedExecutable); + boolean isConstructor = methodName.equals(CONSTRUCTOR_NAME); + + if (methodName.equals(CLASS_INITIALIZER_NAME)) { + /* Class initializers cannot have a special compilation behavior. */ + return; + } - AnnotationMirror compilationBehavior = getAnnotation(annotatedExecutable, getType(ANNOTATION_CLASS_NAME)); var foobar = getAnnotationValue(compilationBehavior, "value", VariableElement.class); assert foobar.getKind() == ElementKind.ENUM_CONSTANT; var name = foobar.getSimpleName().toString(); @@ -104,7 +113,7 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { @Override public void duringSetup(DuringSetupAccess access) { - var method = ReflectionUtil.lookupMethod(%5$s, "%6$s"%7$s); + var method = ReflectionUtil.lookup%5$s(%6$s%7$s%8$s); LayeredCompilationSupport.singleton().registerCompilationBehavior(method, LayeredCompilationBehavior.Behavior.PINNED_TO_INITIAL_LAYER); } } @@ -113,19 +122,20 @@ public void duringSetup(DuringSetupAccess access) { computeAnnotatedMethodName(annotatedExecutable), ANNOTATION_CLASS_NAME, featureClassName, + isConstructor ? "Constructor" : "Method", computeDeclaringClass(annotatedExecutable), - computeMethodName(annotatedExecutable), + isConstructor ? "" : ", \"%s\"".formatted(methodName), computeParams(annotatedExecutable)); out.print(classContents); } } - private static String computeFileName(ExecutableElement e) { + private String computeFileName(ExecutableElement e) { TypeElement enclosingElement = (TypeElement) e.getEnclosingElement(); StringBuilder fileName = new StringBuilder(AutomaticallyRegisteredImageSingletonProcessor.getTypeNameWithEnclosingClasses(enclosingElement, "")); String methodName = e.getSimpleName().toString(); - if ("".equals(methodName)) { + if (CONSTRUCTOR_NAME.equals(methodName)) { methodName = enclosingElement.getSimpleName().toString(); } fileName.append(METHOD_NAME_COMPONENT_SEPARATOR).append(methodName).append(METHOD_NAME_COMPONENT_SEPARATOR).append(getDescriptorForClass(e.getReturnType())); @@ -155,15 +165,15 @@ private static String computeMethodName(ExecutableElement e) { return e.getSimpleName().toString(); } - private static String computeParams(ExecutableElement e) { - String params = String.join(", ", e.getParameters().stream().map(p -> p.asType().toString() + ".class").toArray(String[]::new)); + private String computeParams(ExecutableElement e) { + String params = String.join(", ", e.getParameters().stream().map(p -> lookupClass(getQualifiedName(p.asType()), p.asType())).toArray(String[]::new)); if (!params.isEmpty()) { return ", " + params; } return ""; } - private static String getDescriptorForClass(TypeMirror c) { + private String getDescriptorForClass(TypeMirror c) { return switch (c.getKind()) { case BOOLEAN -> "Z"; case BYTE -> "B"; @@ -174,12 +184,33 @@ private static String getDescriptorForClass(TypeMirror c) { case FLOAT -> "F"; case DOUBLE -> "D"; case VOID -> "V"; + case TYPEVAR -> "Ljava_lang_Object"; case ARRAY -> ARRAY_IDENTIFIER + getDescriptorForClass(((ArrayType) c).getComponentType()); - case DECLARED -> "L" + c.toString().replace('.', OBJECT_PATH_SEPARATOR); + case DECLARED -> "L" + getQualifiedName(c).replace('.', OBJECT_PATH_SEPARATOR).replace('$', OBJECT_PATH_SEPARATOR); default -> throw new RuntimeException("Unexpected null type: " + c); }; } + private String getQualifiedName(TypeMirror c) { + return switch (c.getKind()) { + case DECLARED -> getQualifiedName((DeclaredType) c); + case ARRAY -> getQualifiedName(((ArrayType) c).getComponentType()) + "[]"; + default -> c.toString(); + }; + } + + private String getQualifiedName(DeclaredType c) { + return processingEnv.getElementUtils().getBinaryName((TypeElement) c.asElement()).toString(); + } + + private static String lookupClass(String clazz, TypeMirror c) { + return switch (c.getKind()) { + case DECLARED -> "ReflectionUtil.lookupClass(\"%s\")".formatted(clazz); + case ARRAY -> "%s.arrayType()".formatted(lookupClass(clazz.substring(0, clazz.length() - 2), ((ArrayType) c).getComponentType())); + default -> clazz + ".class"; + }; + } + @Override protected boolean doProcess(Set annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { @@ -188,7 +219,8 @@ protected boolean doProcess(Set annotations, RoundEnviron for (Element element : roundEnv.getElementsAnnotatedWith(getTypeElement(ANNOTATION_CLASS_NAME))) { assert element.getKind().isExecutable(); - processElement((ExecutableElement) element); + AnnotationMirror compilationBehavior = getAnnotation(element, getType(ANNOTATION_CLASS_NAME)); + processElement((ExecutableElement) element, compilationBehavior); } return true; } From db883d84fd45f41b408571f23fc34dcbbb19e7af Mon Sep 17 00:00:00 2001 From: Sacha Coppey Date: Thu, 30 Oct 2025 14:54:06 +0100 Subject: [PATCH 2/2] Check that unexpected core types cannot be reachable nor instantiated in extension layer --- .../graal/pointsto/PointsToAnalysis.java | 7 +- .../pointsto/results/TypeFlowSimplifier.java | 9 +- .../svm/core/InvalidMethodPointerHandler.java | 3 + .../com/oracle/svm/core/JavaMainWrapper.java | 7 +- .../jdk/management/ManagementSupport.java | 4 + .../serialize/SerializationSupport.java | 4 + .../hosted/ExtensionLayerImageFeature.java | 100 ++++++++++++++++++ .../imagelayer/InitialLayerFeature.java | 17 ++- .../imagelayer/SVMImageLayerLoader.java | 31 ++++++ 9 files changed, 170 insertions(+), 12 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java index 1dc9d9511bee..b0eb2fa7ed60 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java @@ -175,8 +175,11 @@ public boolean isClosed(AnalysisType type) { /* In a closed type world all subtypes known. */ return true; } - /* Array and leaf types are by definition closed. */ - return type.isArray() || type.isLeaf(); + /* + * Array and leaf types are by definition closed. Core types are considered as closed to + * allow optimizations to be applied on them. + */ + return type.isArray() || type.isLeaf() || hostVM.isCoreType(type); } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/TypeFlowSimplifier.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/TypeFlowSimplifier.java index 97ac2dedc655..3f1a68d7224c 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/TypeFlowSimplifier.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/TypeFlowSimplifier.java @@ -347,18 +347,13 @@ private void handleInvoke(Invoke invoke, SimplifierTool tool) { * 2. The receiver TypeFlow's type is a closed type, so it may be not extended in a later * layer. * - * 3. The receiver TypeFlow's type is a core type, so it may be not extended in a later - * layer. (GR-70846: This check condition will be merged with the previous one once core - * types are fully considered as closed.) - * - * 4. The receiver TypeFlow is not saturated. + * 3. The receiver TypeFlow is not saturated. * * Otherwise, when the receiver's analysis results are incomplete, then it is possible for * more types to be observed in subsequent layers. */ boolean receiverAnalysisResultsComplete = strengthenGraphs.isClosedTypeWorld || - (hasReceiver && (analysis.isClosed(invokeFlow.getReceiverType()) || analysis.getHostVM().isCoreType(invokeFlow.getReceiverType()) || - !methodFlow.isSaturated(analysis, invokeFlow.getReceiver()))); + (hasReceiver && (analysis.isClosed(invokeFlow.getReceiverType()) || !methodFlow.isSaturated(analysis, invokeFlow.getReceiver()))); if (callTarget.invokeKind().isDirect()) { /* diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/InvalidMethodPointerHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/InvalidMethodPointerHandler.java index f60c4803091f..522c70dd37b1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/InvalidMethodPointerHandler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/InvalidMethodPointerHandler.java @@ -35,6 +35,8 @@ import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.word.Pointer; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior.Behavior; import com.oracle.svm.core.graal.code.StubCallingConvention; import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.log.Log; @@ -79,6 +81,7 @@ private static void invalidCodeAddressHandler() { @StubCallingConvention @NeverInline("We need a separate frame that stores all registers") @Uninterruptible(reason = "Precaution.") + @LayeredCompilationBehavior(Behavior.FULLY_DELAYED_TO_APPLICATION_LAYER) private static void invalidVTableEntryHandler() { Pointer callerSP = KnownIntrinsics.readCallerStackPointer(); CodePointer callerIP = KnownIntrinsics.readReturnAddress(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java index 3c112cefd8c4..d14d078e0032 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java @@ -56,6 +56,7 @@ import org.graalvm.word.WordBase; import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior.Behavior; import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.c.CGlobalDataFactory; import com.oracle.svm.core.c.function.CEntryPointActions; @@ -166,7 +167,7 @@ public List getInputArguments() { * For layered images this method is delayed until the application layer. This is necessary so * that the method handle can be inlined before analysis. */ - @LayeredCompilationBehavior(LayeredCompilationBehavior.Behavior.FULLY_DELAYED_TO_APPLICATION_LAYER) + @LayeredCompilationBehavior(Behavior.FULLY_DELAYED_TO_APPLICATION_LAYER) public static void invokeMain(String[] args) throws Throwable { String[] mainArgs = args; if (ImageSingletons.contains(PreMainSupport.class)) { @@ -276,9 +277,11 @@ private static void runShutdown0() { } } + /** The entry point of the image needs to be in the application layer. */ @Uninterruptible(reason = "Thread state not set up yet.") @CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class) @CEntryPointOptions(prologue = NoPrologue.class, epilogue = NoEpilogue.class) + @LayeredCompilationBehavior(Behavior.FULLY_DELAYED_TO_APPLICATION_LAYER) public static int run(int argc, CCharPointerPointer argv) { if (SubstrateOptions.RunMainInNewThread.getValue()) { return doRunInNewThread(argc, argv); @@ -289,7 +292,7 @@ public static int run(int argc, CCharPointerPointer argv) { /** SVM start-up logic should be pinned to the initial layer. */ @Uninterruptible(reason = "Thread state not setup yet.") - @LayeredCompilationBehavior(LayeredCompilationBehavior.Behavior.PINNED_TO_INITIAL_LAYER) + @LayeredCompilationBehavior(Behavior.PINNED_TO_INITIAL_LAYER) private static int doRun(int argc, CCharPointerPointer argv) { try { CPUFeatureAccess cpuFeatureAccess = ImageSingletons.lookup(CPUFeatureAccess.class); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementSupport.java index 74a21f09366b..85a52f0ea859 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementSupport.java @@ -53,6 +53,8 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.impl.InternalPlatform; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior.Behavior; import com.oracle.svm.core.GCRelatedMXBeans; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; @@ -136,6 +138,7 @@ public static ManagementSupport getSingleton() { return ImageSingletons.lookup(ManagementSupport.class); } + @LayeredCompilationBehavior(Behavior.PINNED_TO_INITIAL_LAYER) public T getPlatformMXBean(Class clazz) { Object result = getPlatformMXBeans0(clazz); if (result == null) { @@ -152,6 +155,7 @@ public PlatformManagedObject getPlatformMXBeanRaw(Class List getPlatformMXBeans(Class clazz) { Object result = getPlatformMXBeans0(clazz); if (result == null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java index d66ba10bb2df..974c5c40c8fb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java @@ -40,8 +40,10 @@ import org.graalvm.nativeimage.dynamicaccess.AccessCondition; import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.BuildPhaseProvider.AfterCompilation; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.configure.RuntimeDynamicAccessMetadata; +import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonSupport; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; @@ -209,12 +211,14 @@ public String getClassLoaderSerializationLookupKey(ClassLoader classLoader) { * Temporary key for maps ideally indexed by their {@link Class} or {@link DynamicHub}. At * runtime, these maps should be indexed by {@link DynamicHub#getTypeID} */ + @Platforms(Platform.HOSTED_ONLY.class) public record DynamicHubKey(DynamicHub hub) { public int getTypeID() { return hub.getTypeID(); } } + @UnknownObjectField(fullyQualifiedTypes = "org.graalvm.collections.EconomicMapImpl", availability = AfterCompilation.class) // private final EconomicMap classes = EconomicMap.create(); private final EconomicMap lambdaCapturingClasses = EconomicMap.create(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ExtensionLayerImageFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ExtensionLayerImageFeature.java index cfd57523e3f0..682807a2bd03 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ExtensionLayerImageFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ExtensionLayerImageFeature.java @@ -24,12 +24,31 @@ */ package com.oracle.svm.hosted; +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Predicate; + +import org.graalvm.collections.EconomicMap; + +import com.oracle.graal.pointsto.api.HostVM; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior; import com.oracle.svm.core.c.BoxedRelocatedPointer; import com.oracle.svm.core.code.ImageCodeInfo; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.traits.BuiltinTraits.BuildtimeAccessOnly; +import com.oracle.svm.core.traits.BuiltinTraits.NoLayeredCallbacks; +import com.oracle.svm.core.traits.SingletonLayeredInstallationKind.Independent; +import com.oracle.svm.core.traits.SingletonTraits; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.BeforeCompilationAccessImpl; +import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; +import com.oracle.svm.hosted.imagelayer.InitialLayerFeature; +import com.oracle.svm.hosted.imagelayer.SVMImageLayerLoader; import com.oracle.svm.util.ReflectionUtil; /** @@ -37,7 +56,9 @@ * better mechanisms to avoid these workarounds. */ @AutomaticallyRegisteredFeature +@SingletonTraits(access = BuildtimeAccessOnly.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Independent.class) final class ExtensionLayerImageFeature implements InternalFeature { + private static final Object NULL_SUPER_CORE_TYPE = new Object(); @Override public boolean isInConfiguration(IsInConfigurationAccess access) { @@ -72,4 +93,83 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { */ access.registerAsInHeap(ReflectionUtil.lookupClass(false, "java.util.concurrent.ConcurrentHashMap$CounterCell")); } + + @Override + public void beforeCompilation(BeforeCompilationAccess a) { + BeforeCompilationAccessImpl access = (BeforeCompilationAccessImpl) a; + AnalysisUniverse universe = access.aUniverse; + HostVM hostVM = universe.hostVM(); + SVMImageLayerLoader imageLayerLoader = HostedImageLayerBuildingSupport.singleton().getLoader(); + EconomicMap superCoreTypes = EconomicMap.create(); + + for (var type : universe.getTypes()) { + /* + * These checks allow to ensure that core types can be treated as closed (see + * PointsToAnalysis.isClosed(AnalysisType)). + */ + boolean coreType = hostVM.isCoreType(type); + if (coreType) { + superCoreTypes.put(type, type); + } + AnalysisType superCoreType = getSuperCoreType(type, hostVM, superCoreTypes); + boolean extendsCoreType = superCoreType != null && !coreType; + if (coreType || extendsCoreType) { + /* + * This checks that all core types and types that extend or implement a core type + * were not marked as reachable or instantiated only in an extension layer, showing + * that the analysis of the core was complete. + */ + checkCondition(type.isReachable(), imageLayerLoader::isReachableInPreviousLayer, type, extendsCoreType, superCoreType, "reachable"); + checkCondition(type.isInstantiated(), imageLayerLoader::isInstantiatedInPreviousLayer, type, extendsCoreType, superCoreType, "instantiated"); + } + } + } + + public AnalysisType getSuperCoreType(AnalysisType type, HostVM hostVM, EconomicMap superCoreTypes) { + /* Check the cache to see if the super core type was already computed. */ + if (superCoreTypes.containsKey(type)) { + Object result = superCoreTypes.get(type); + return result == NULL_SUPER_CORE_TYPE ? null : (AnalysisType) result; + } + + AnalysisType result = null; + + /* Go through the super types to check if one of them is a core type. */ + AnalysisType superType = type.getSuperclass(); + if (superType != null) { + result = hostVM.isCoreType(superType) ? superType : getSuperCoreType(superType, hostVM, superCoreTypes); + } + + if (result == null) { + /* + * If no result was found, iterate through all interfaces to see if one of them is a + * core type. + */ + AnalysisType[] interfaces = type.getInterfaces(); + var coreInterface = Arrays.stream(interfaces).map(i -> getSuperCoreType(i, hostVM, superCoreTypes)).filter(Objects::nonNull).findFirst(); + if (coreInterface.isPresent()) { + result = coreInterface.get(); + } + } + + /* Cache the result. */ + superCoreTypes.put(type, result == null ? NULL_SUPER_CORE_TYPE : result); + + return result; + } + + private static void checkCondition(boolean condition, Predicate test, AnalysisType type, boolean extendsCoreType, AnalysisType superCoreType, String kind) { + if (condition) { + String hint = "Please make sure that all core types are seen by the initial layer analysis by either registering more entry points using " + + LayeredCompilationBehavior.class + " with the " + LayeredCompilationBehavior.Behavior.PINNED_TO_INITIAL_LAYER + + " value or by explicitly registering the entry point or type reachability in " + InitialLayerFeature.class; + if (extendsCoreType) { + VMError.guarantee(test.test(type), "The type %s is extending the core type %s which was not seen as %s the initial layer. " + + "It is illegal to extend core types in subsequent layers. %s", type, superCoreType, kind, hint); + } else { + VMError.guarantee(test.test(type), "The core type %s was not seen as %s the initial layer. " + + "It is illegal for core types to become reachable in subsequent layers. %s", type, kind, hint); + } + } + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/InitialLayerFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/InitialLayerFeature.java index 656815a03de3..cfaf4b4ec9f8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/InitialLayerFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/InitialLayerFeature.java @@ -26,14 +26,21 @@ import static com.oracle.svm.common.layeredimage.LayeredCompilationBehavior.Behavior.PINNED_TO_INITIAL_LAYER; +import java.lang.reflect.Proxy; + import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.svm.common.hosted.layeredimage.LayeredCompilationSupport; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.bootstrap.BootstrapMethodInfo; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.util.ReflectionUtil; +import jdk.internal.loader.ClassLoaders; import jdk.internal.misc.Unsafe; @AutomaticallyRegisteredFeature @@ -43,8 +50,10 @@ public boolean isInConfiguration(Feature.IsInConfigurationAccess access) { return ImageLayerBuildingSupport.buildingInitialLayer(); } + @SuppressWarnings("deprecation") @Override - public void duringSetup(DuringSetupAccess access) { + public void duringSetup(DuringSetupAccess a) { + DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; /* * Make sure that critical VM components are included in the base layer by registering * runtime APIs as entry points. Although the types below are part of java.base, so they @@ -57,6 +66,12 @@ public void duringSetup(DuringSetupAccess access) { compilationSupport.registerCompilationBehavior(ReflectionUtil.lookupMethod(Runtime.class, "getRuntime"), PINNED_TO_INITIAL_LAYER); compilationSupport.registerCompilationBehavior(ReflectionUtil.lookupMethod(Runtime.class, "gc"), PINNED_TO_INITIAL_LAYER); compilationSupport.registerCompilationBehavior(ReflectionUtil.lookupMethod(Class.class, "getResource", String.class), PINNED_TO_INITIAL_LAYER); + + AnalysisMetaAccess metaAccess = access.getMetaAccess(); + metaAccess.lookupJavaType(Uninterruptible.class).registerAsReachable("Core type"); + metaAccess.lookupJavaType(Proxy.getProxyClass(ClassLoaders.appClassLoader(), Uninterruptible.class)).registerAsInstantiated("Core type"); + metaAccess.lookupJavaType(BootstrapMethodInfo.class).registerAsInstantiated("Core type"); + metaAccess.lookupJavaType(BootstrapMethodInfo.ExceptionWrapper.class).registerAsInstantiated("Core type"); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java index 21c8a1caea0e..af29a32974c3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java @@ -109,6 +109,7 @@ import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisMethod.WrappedMethod; import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisMethod.WrappedMethod.WrappedMember; import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType; +import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType.Reader; import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType.WrappedType; import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnalysisType.WrappedType.SerializationGenerated; import com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.PersistedAnnotationElement; @@ -1858,6 +1859,36 @@ private void scanCompanionField(DynamicHub hub) { instance.readFieldValue(metaAccess.lookupJavaField(dynamicHubCompanionField)); } + public boolean isReachableInPreviousLayer(AnalysisType type) { + return getPropertyInPreviousLayer(type, Reader::getIsReachable); + } + + public boolean isInstantiatedInPreviousLayer(AnalysisType type) { + return getPropertyInPreviousLayer(type, Reader::getIsInstantiated); + } + + private boolean getPropertyInPreviousLayer(AnalysisType type, Function propertyGetter) { + Integer typeId; + if (type.getWrapped() instanceof BaseLayerType baseLayerType) { + typeId = baseLayerType.getBaseLayerId(); + } else { + /* + * Types that cannot be loaded manually can be duplicated and can get a new type id even + * if they were in a shared layer. In this case, using the type identifier can still + * retrieve the id from the shared layer. + */ + String typeDescriptor = imageLayerSnapshotUtil.getTypeDescriptor(type); + typeId = typeDescriptorToBaseLayerId.get(typeDescriptor); + } + if (typeId != null) { + var typeInfo = findType(typeId); + if (typeInfo != null) { + return propertyGetter.apply(typeInfo); + } + } + return false; + } + public record LayeredSimulationResult(boolean successful, EconomicMap staticFieldValues) { }