diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java index 981e7b4d3a48..34c3e40883b2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java @@ -353,14 +353,14 @@ public boolean isDelayed() { return compilationBehavior == CompilationBehavior.FULLY_DELAYED_TO_APPLICATION_LAYER && buildingSharedLayer; } - public void setPinnedToInitialLayer() { + public void setPinnedToInitialLayer(Object reason) { BigBang bigbang = getUniverse().getBigbang(); AnalysisError.guarantee(bigbang.getHostVM().buildingInitialLayer(), "Methods can only be pinned to the initial layer: %s", this); boolean nonAbstractInstanceClass = !declaringClass.isArray() && declaringClass.isInstanceClass() && !declaringClass.isAbstract(); - AnalysisError.guarantee(nonAbstractInstanceClass, "Only methods from non abstract instance class can be delayed: %s", this); - bigbang.forcedAddRootMethod(this, true, "Method is pinned to the initial layer"); + AnalysisError.guarantee(nonAbstractInstanceClass, "Only methods from non abstract instance class can be pinned: %s", this); + bigbang.forcedAddRootMethod(this, true, "pinned to initial layer: " + reason); if (!isStatic()) { - declaringClass.registerAsInstantiated(this + " is pinned to the initial layer"); + declaringClass.registerAsInstantiated("declared method " + this.format("%H.%n(%p)") + " is pinned to initial layer: " + reason); } setNewCompilationBehavior(CompilationBehavior.PINNED_TO_INITIAL_LAYER); } 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 b041a8395cbf..41d0467d157e 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 @@ -1110,7 +1110,10 @@ protected void setupNativeImage(OptionValues options, Map bb.tryRegisterTypeForBaseImage(originalMetaAccess.lookupJavaType(clazz))); + if (ImageLayerBuildingSupport.buildingSharedLayer()) { + HostedImageLayerBuildingSupport.singleton().registerBaseLayerTypes(bb, originalMetaAccess, loader.classLoaderSupport); + } + if (loader.classLoaderSupport.isPreserveMode()) { PreserveOptionsSupport.registerPreservedClasses(bb, originalMetaAccess, loader.classLoaderSupport); } @@ -1244,71 +1247,8 @@ public static void initializeBigBang(Inflation bb, OptionValues options, Feature HostedImageLayerBuildingSupport.singleton().getLoader().relinkNonTransformedStaticFinalFieldValues(); } - /* - * System classes and fields are necessary to tell the static analysis that certain things - * really "exist". The most common reason for that is that there are no instances and - * allocations of these classes seen during the static analysis. The heap chunks are one - * good example. - */ try (Indent ignored = debug.logAndIndent("add initial classes/fields/methods")) { - bb.addRootClass(Object.class, false, false).registerAsInstantiated("root class"); - bb.addRootField(DynamicHub.class, "vtable"); - bb.addRootClass(String.class, false, false).registerAsInstantiated("root class"); - bb.addRootClass(String[].class, false, false).registerAsInstantiated("root class"); - bb.addRootField(String.class, "value").registerAsInstantiated("root class"); - bb.addRootClass(long[].class, false, false).registerAsInstantiated("root class"); - bb.addRootClass(byte[].class, false, false).registerAsInstantiated("root class"); - bb.addRootClass(byte[][].class, false, false).registerAsInstantiated("root class"); - bb.addRootClass(Object[].class, false, false).registerAsInstantiated("root class"); - bb.addRootClass(CFunctionPointer[].class, false, false).registerAsInstantiated("root class"); - bb.addRootClass(PointerBase[].class, false, false).registerAsInstantiated("root class"); - - /* MethodRef can conceal use of MethodPointer and MethodOffset until after analysis. */ - bb.addRootClass(MethodPointer.class, false, true); - if (SubstrateOptions.useRelativeCodePointers()) { - bb.addRootClass(MethodOffset.class, false, true); - } - - bb.addRootMethod(ReflectionUtil.lookupMethod(SubstrateArraycopySnippets.class, "doArraycopy", Object.class, int.class, Object.class, int.class, int.class), true, - "Runtime support, registered in " + NativeImageGenerator.class); - bb.addRootMethod(ReflectionUtil.lookupMethod(Object.class, "getClass"), true, "Runtime support, registered in " + NativeImageGenerator.class); - - for (JavaKind kind : JavaKind.values()) { - if (kind.isPrimitive() && kind != JavaKind.Void) { - bb.addRootClass(kind.toJavaClass(), false, true); - bb.addRootClass(kind.toBoxedJavaClass(), false, true).registerAsInstantiated("root class"); - bb.addRootField(kind.toBoxedJavaClass(), "value"); - bb.addRootMethod(ReflectionUtil.lookupMethod(kind.toBoxedJavaClass(), "valueOf", kind.toJavaClass()), true, "Runtime support, registered in " + NativeImageGenerator.class); - bb.addRootMethod(ReflectionUtil.lookupMethod(kind.toBoxedJavaClass(), kind.getJavaName() + "Value"), true, "Runtime support, registered in " + NativeImageGenerator.class); - /* - * Register the cache location as reachable. - * BoxingSnippets$Templates#getCacheLocation accesses the cache field. - */ - Class[] innerClasses = kind.toBoxedJavaClass().getDeclaredClasses(); - if (innerClasses != null && innerClasses.length > 0) { - bb.getMetaAccess().lookupJavaType(innerClasses[0]).registerAsReachable("inner class of root class"); - } - } - } - /* SubstrateTemplates#toLocationIdentity accesses the Counter.value field. */ - bb.getMetaAccess().lookupJavaType(JavaKind.Void.toJavaClass()).registerAsReachable("root class"); - bb.getMetaAccess().lookupJavaType(com.oracle.svm.core.util.Counter.class).registerAsReachable("root class"); - bb.getMetaAccess().lookupJavaType(com.oracle.svm.core.allocationprofile.AllocationCounter.class).registerAsReachable("root class"); - /* - * SubstrateAllocationProfilingData is not actually present in the image since it is - * only allocated at build time, is passed to snippets as a @ConstantParameter, and it - * only contains final fields that are constant-folded. However, since the profiling - * object is only allocated during lowering it is processed by the shadow heap after - * analysis, so its type needs to be already marked reachable at this point. - */ - bb.getMetaAccess().lookupJavaType(com.oracle.svm.core.graal.snippets.SubstrateAllocationSnippets.SubstrateAllocationProfilingData.class).registerAsReachable("root class"); - /* - * Similarly to above, StackSlotIdentity only gets reachable during lowering, through - * build time allocated constants. It doesn't actually end up in the image heap since - * all its fields are final and are constant-folded, but the type becomes reachable, - * through the shadow heap processing, after analysis. - */ - bb.getMetaAccess().lookupJavaType(com.oracle.svm.core.graal.stackvalue.StackValueNode.StackSlotIdentity.class).registerAsReachable("root class"); + registerRootElements(bb); NativeImageGenerator.registerGraphBuilderPlugins(featureHandler, null, aProviders, aMetaAccess, aUniverse, nativeLibraries, loader, ParsingReason.PointsToAnalysis, bb.getAnnotationSubstitutionProcessor(), classInitializationPlugin, ConfigurationValues.getTarget(), supportsStubBasedPlugins); @@ -1318,6 +1258,75 @@ public static void initializeBigBang(Inflation bb, OptionValues options, Feature } } + /** + * System classes and fields are necessary to tell the static analysis that certain things + * really "exist". The most common reason for that is that there are no instances and + * allocations of these classes seen during the static analysis. The heap chunks are one good + * example. + */ + private static void registerRootElements(Inflation bb) { + String rootClassReason = "system class included unconditionally"; + String rootMethodReason = "system method included unconditionally"; + bb.addRootClass(Object.class, false, false).registerAsInstantiated(rootClassReason); + bb.addRootField(DynamicHub.class, "vtable"); + bb.addRootClass(String.class, false, false).registerAsInstantiated(rootClassReason); + bb.addRootClass(String[].class, false, false).registerAsInstantiated(rootClassReason); + bb.addRootField(String.class, "value").registerAsInstantiated(rootClassReason); + bb.addRootClass(long[].class, false, false).registerAsInstantiated(rootClassReason); + bb.addRootClass(byte[].class, false, false).registerAsInstantiated(rootClassReason); + bb.addRootClass(byte[][].class, false, false).registerAsInstantiated(rootClassReason); + bb.addRootClass(Object[].class, false, false).registerAsInstantiated(rootClassReason); + bb.addRootClass(CFunctionPointer[].class, false, false).registerAsInstantiated(rootClassReason); + bb.addRootClass(PointerBase[].class, false, false).registerAsInstantiated(rootClassReason); + + /* MethodRef can conceal use of MethodPointer and MethodOffset until after analysis. */ + bb.addRootClass(MethodPointer.class, false, true); + if (SubstrateOptions.useRelativeCodePointers()) { + bb.addRootClass(MethodOffset.class, false, true); + } + + bb.addRootMethod(ReflectionUtil.lookupMethod(SubstrateArraycopySnippets.class, "doArraycopy", + Object.class, int.class, Object.class, int.class, int.class), true, rootMethodReason); + bb.addRootMethod(ReflectionUtil.lookupMethod(Object.class, "getClass"), true, rootMethodReason); + + for (JavaKind kind : JavaKind.values()) { + if (kind.isPrimitive() && kind != JavaKind.Void) { + bb.addRootClass(kind.toJavaClass(), false, true); + bb.addRootClass(kind.toBoxedJavaClass(), false, true).registerAsInstantiated(rootClassReason); + bb.addRootField(kind.toBoxedJavaClass(), "value"); + bb.addRootMethod(ReflectionUtil.lookupMethod(kind.toBoxedJavaClass(), "valueOf", kind.toJavaClass()), true, rootMethodReason); + bb.addRootMethod(ReflectionUtil.lookupMethod(kind.toBoxedJavaClass(), kind.getJavaName() + "Value"), true, rootMethodReason); + /* + * Register the cache location as reachable. + * BoxingSnippets$Templates#getCacheLocation accesses the cache field. + */ + Class[] innerClasses = kind.toBoxedJavaClass().getDeclaredClasses(); + if (innerClasses != null && innerClasses.length > 0) { + bb.getMetaAccess().lookupJavaType(innerClasses[0]).registerAsReachable("inner class of " + rootClassReason); + } + } + } + /* SubstrateTemplates#toLocationIdentity accesses the Counter.value field. */ + bb.getMetaAccess().lookupJavaType(JavaKind.Void.toJavaClass()).registerAsReachable(rootClassReason); + bb.getMetaAccess().lookupJavaType(com.oracle.svm.core.util.Counter.class).registerAsReachable(rootClassReason); + bb.getMetaAccess().lookupJavaType(com.oracle.svm.core.allocationprofile.AllocationCounter.class).registerAsReachable(rootClassReason); + /* + * SubstrateAllocationProfilingData is not actually present in the image since it is only + * allocated at build time, is passed to snippets as a @ConstantParameter, and it only + * contains final fields that are constant-folded. However, since the profiling object is + * only allocated during lowering it is processed by the shadow heap after analysis, so its + * type needs to be already marked reachable at this point. + */ + bb.getMetaAccess().lookupJavaType(com.oracle.svm.core.graal.snippets.SubstrateAllocationSnippets.SubstrateAllocationProfilingData.class).registerAsReachable(rootClassReason); + /* + * Similarly to above, StackSlotIdentity only gets reachable during lowering, through build + * time allocated constants. It doesn't actually end up in the image heap since all its + * fields are final and are constant-folded, but the type becomes reachable, through the + * shadow heap processing, after analysis. + */ + bb.getMetaAccess().lookupJavaType(com.oracle.svm.core.graal.stackvalue.StackValueNode.StackSlotIdentity.class).registerAsReachable(rootClassReason); + } + public static void performSnippetGraphAnalysis(BigBang bb, SubstrateReplacements replacements, OptionValues options, Function objectTransformer) { Collection snippetGraphs = replacements.getSnippetGraphs(GraalOptions.TrackNodeSourcePosition.getValue(options), options, objectTransformer); if (bb instanceof NativeImagePointsToAnalysis pointsToAnalysis) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java index 8d5f448a2d57..b0ffc4878888 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java @@ -39,6 +39,7 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platform.LINUX_AMD64; +import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.svm.core.SubstrateOptions; @@ -72,6 +73,7 @@ import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionValues; import jdk.graal.compiler.options.OptionsContainer; +import jdk.vm.ci.meta.MetaAccessProvider; public final class HostedImageLayerBuildingSupport extends ImageLayerBuildingSupport { @@ -285,9 +287,9 @@ private static boolean isLayerUseOptionEnabled(OptionValues values) { return false; } + /** Currently layered images are only supported on {@link LINUX_AMD64}. */ private static boolean supportedPlatform(Platform platform) { - boolean supported = platform instanceof LINUX_AMD64; - return supported; + return platform instanceof LINUX_AMD64; } public static HostedImageLayerBuildingSupport initialize(HostedOptionValues values, ImageClassLoader imageClassLoader, Path builderTempDir) { @@ -395,4 +397,8 @@ public static void setupSharedLayerLibrary(NativeLibraries nativeLibs) { HostedDynamicLayerInfo.singleton().registerLibName(libName); nativeLibs.addDynamicNonJniLibrary(libName); } + + public void registerBaseLayerTypes(BigBang bb, MetaAccessProvider originalMetaAccess, NativeImageClassLoaderSupport classLoaderSupport) { + classLoaderSupport.getClassesToIncludeUnconditionally().forEach(clazz -> bb.tryRegisterTypeForBaseImage(originalMetaAccess.lookupJavaType(clazz))); + } } 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 new file mode 100644 index 000000000000..9a228796d93f --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/InitialLayerFeature.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.imagelayer; + +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import org.graalvm.nativeimage.hosted.Feature; + +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.BeforeAnalysisAccessImpl; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.internal.misc.Unsafe; + +@AutomaticallyRegisteredFeature +public class InitialLayerFeature implements InternalFeature { + @Override + public boolean isInConfiguration(Feature.IsInConfigurationAccess access) { + return ImageLayerBuildingSupport.buildingInitialLayer(); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess a) { + BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) 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 + * would anyway be included in every base layer created with module=java.base, this ensures + * that the base layer is usable regardless of the class inclusion policy. + */ + String pinReason = "base layer entry point included unconditionally"; + AnalysisMetaAccess metaAccess = access.getMetaAccess(); + metaAccess.lookupJavaMethod(ReflectionUtil.lookupMethod(Unsafe.class, "getUnsafe")).setPinnedToInitialLayer(pinReason); + metaAccess.lookupJavaMethod(ReflectionUtil.lookupMethod(Unsafe.class, "allocateInstance", Class.class)).setPinnedToInitialLayer(pinReason); + metaAccess.lookupJavaMethod(ReflectionUtil.lookupMethod(Runtime.class, "getRuntime")).setPinnedToInitialLayer(pinReason); + metaAccess.lookupJavaMethod(ReflectionUtil.lookupMethod(Runtime.class, "gc")).setPinnedToInitialLayer(pinReason); + metaAccess.lookupJavaMethod(ReflectionUtil.lookupMethod(Class.class, "getResource", String.class)).setPinnedToInitialLayer(pinReason); + } + +}