diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index aeed2a91c2a8..736ff7ae033d 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -2308,6 +2308,10 @@ org.graalvm.nativeimage.foreign, org.graalvm.truffle.runtime.svm, com.oracle.truffle.enterprise.svm""", + """com.oracle.svm.common.hosted.layeredimage to org.graalvm.nativeimage.pointsto, + org.graalvm.nativeimage.builder""", + """com.oracle.svm.common.layeredimage to org.graalvm.nativeimage.pointsto, + org.graalvm.nativeimage.builder""", ], }, "noMavenJavadoc": True, 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 67210181eaa0..6addd283136d 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 @@ -65,6 +65,9 @@ import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.graal.pointsto.util.AtomicUtils; import com.oracle.graal.pointsto.util.ConcurrentLightHashSet; +import com.oracle.svm.common.hosted.layeredimage.LayeredCompilationSupport; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior.Behavior; import com.oracle.svm.common.meta.MultiMethod; import jdk.graal.compiler.debug.DebugContext; @@ -198,7 +201,7 @@ public record Signature(String name, AnalysisType[] parameterTypes) { */ private boolean hasOpaqueReturn; - private CompilationBehavior compilationBehavior = CompilationBehavior.DEFAULT; + private LayeredCompilationBehavior.Behavior compilationBehavior; @SuppressWarnings({"this-escape", "unchecked"}) protected AnalysisMethod(AnalysisUniverse universe, ResolvedJavaMethod wrapped, MultiMethodKey multiMethodKey, Map multiMethodMap) { @@ -280,6 +283,19 @@ protected AnalysisMethod(AnalysisUniverse universe, ResolvedJavaMethod wrapped, parsingContextMaxDepth = universe.analysisPolicy().parsingContextMaxDepth(); this.enableReachableInCurrentLayer = universe.hostVM.enableReachableInCurrentLayer(); + compilationBehavior = LayeredCompilationBehavior.Behavior.DEFAULT; + if (universe.hostVM.buildingImageLayer()) { + var annotationExtractor = universe.getAnnotationExtractor(); + if (annotationExtractor.hasAnnotation(wrapped, LayeredCompilationBehavior.class)) { + LayeredCompilationBehavior behavior = annotationExtractor.extractAnnotation(wrapped, LayeredCompilationBehavior.class, true); + compilationBehavior = behavior.value(); + if (compilationBehavior == LayeredCompilationBehavior.Behavior.PINNED_TO_INITIAL_LAYER && universe.hostVM.buildingExtensionLayer() && !isInBaseLayer) { + var errorMessage = String.format("User methods with layered compilation behavior %s must be registered via %s in the initial layer", + LayeredCompilationBehavior.Behavior.PINNED_TO_INITIAL_LAYER, LayeredCompilationSupport.class); + throw AnalysisError.userError(errorMessage); + } + } + } } @SuppressWarnings("this-escape") @@ -314,21 +330,21 @@ protected AnalysisMethod(AnalysisMethod original, MultiMethodKey multiMethodKey) } /** - * This method should not be used directly, except to set the {@link CompilationBehavior} from a - * previous layer. To set a new {@link CompilationBehavior}, please use the associated setter. + * This method should not be used directly, except to set the {@link Behavior} from a previous + * layer. To set a new {@link Behavior}, please use the associated setter. */ - public void setCompilationBehavior(CompilationBehavior compilationBehavior) { + public void setCompilationBehavior(LayeredCompilationBehavior.Behavior compilationBehavior) { assert getUniverse().getBigbang().getHostVM().buildingImageLayer() : "The method compilation behavior can only be set in layered images"; this.compilationBehavior = compilationBehavior; } - private void setNewCompilationBehavior(CompilationBehavior compilationBehavior) { - assert (!isInBaseLayer && this.compilationBehavior == CompilationBehavior.DEFAULT) || this.compilationBehavior == compilationBehavior : "The method was already assigned " + + private void setNewCompilationBehavior(LayeredCompilationBehavior.Behavior compilationBehavior) { + assert (!isInBaseLayer && this.compilationBehavior == LayeredCompilationBehavior.Behavior.DEFAULT) || this.compilationBehavior == compilationBehavior : "The method was already assigned " + this.compilationBehavior + ", but trying to assign " + compilationBehavior; setCompilationBehavior(compilationBehavior); } - public CompilationBehavior getCompilationBehavior() { + public LayeredCompilationBehavior.Behavior getCompilationBehavior() { return compilationBehavior; } @@ -342,7 +358,7 @@ public void setFullyDelayedToApplicationLayer() { AnalysisError.guarantee(parsedGraphCacheState.get() == GraphCacheEntry.UNPARSED, "The method %s was marked as delayed to the application layer but was already parsed", this); AnalysisError.guarantee(!hostVM.hasAlwaysInlineDirective(this), "Method %s with an always inline directive cannot be delayed to the application layer as such methods cannot be inlined", this); AnalysisError.guarantee(isConcrete(), "Method %s is not concrete and cannot be delayed to the application layer", this); - setNewCompilationBehavior(CompilationBehavior.FULLY_DELAYED_TO_APPLICATION_LAYER); + setNewCompilationBehavior(LayeredCompilationBehavior.Behavior.FULLY_DELAYED_TO_APPLICATION_LAYER); } /** @@ -350,27 +366,27 @@ public void setFullyDelayedToApplicationLayer() { * layer is a shared layer. */ public boolean isDelayed() { - return compilationBehavior == CompilationBehavior.FULLY_DELAYED_TO_APPLICATION_LAYER && buildingSharedLayer; + return compilationBehavior == LayeredCompilationBehavior.Behavior.FULLY_DELAYED_TO_APPLICATION_LAYER && buildingSharedLayer; } /** * Ensures this method is compiled in the initial layer. See - * {@link CompilationBehavior#PINNED_TO_INITIAL_LAYER} for more details. + * {@link Behavior#PINNED_TO_INITIAL_LAYER} for more details. */ - public void setPinnedToInitialLayer(Object reason) { + public void setPinnedToInitialLayer() { 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 pinned: %s", this); - bigbang.forcedAddRootMethod(this, true, "pinned to initial layer: " + reason); + bigbang.forcedAddRootMethod(this, true, "pinned to initial layer"); if (!isStatic()) { - declaringClass.registerAsInstantiated("declared method " + this.format("%H.%n(%p)") + " is pinned to initial layer: " + reason); + declaringClass.registerAsInstantiated("declared method " + this.format("%H.%n(%p)") + " is pinned to initial layer"); } - setNewCompilationBehavior(CompilationBehavior.PINNED_TO_INITIAL_LAYER); + setNewCompilationBehavior(LayeredCompilationBehavior.Behavior.PINNED_TO_INITIAL_LAYER); } public boolean isPinnedToInitialLayer() { - return compilationBehavior == CompilationBehavior.PINNED_TO_INITIAL_LAYER; + return compilationBehavior == LayeredCompilationBehavior.Behavior.PINNED_TO_INITIAL_LAYER; } private static String createName(ResolvedJavaMethod wrapped, MultiMethodKey multiMethodKey) { @@ -1432,40 +1448,4 @@ public boolean hasOpaqueReturn() { } protected abstract AnalysisMethod createMultiMethod(AnalysisMethod analysisMethod, MultiMethodKey newMultiMethodKey); - - /** - * This state represents how a method should be compiled in layered images. The state of a - * method can only be decided in the first layer if it is marked as tracked across layers. The - * state has to stay the same across all the extension layers. If not specified, the state of a - * method will be {@link CompilationBehavior#DEFAULT}. - */ - public enum CompilationBehavior { - - /** - * Method remains unanalyzed until the application layer and any inlining in a shared layer - * is prevented. A call to the method in a shared layer will be replaced by an indirect - * call. The compilation of those methods is then forced in the application layer and the - * corresponding symbol is declared as global. - * - * A delayed method that is not referenced in any shared layer is treated as a - * {@link CompilationBehavior#DEFAULT} method in the application layer and does not have to - * be compiled. If it is only referenced in the application layer, it might be inlined and - * not compiled at all. - */ - FULLY_DELAYED_TO_APPLICATION_LAYER, - - /** - * Method can be inlined into other methods, both before analysis and during compilation, - * and will be compiled as a distinct compilation unit as stipulated by the normal native - * image generation process (i.e., the method is installed as a root and/or a reference to - * the method exists via a call and/or an explicit MethodReference). - */ - DEFAULT, - - /** - * Method is pinned to the initial layer, meaning it has to be analyzed and compiled in this - * specific layer. - */ - PINNED_TO_INITIAL_LAYER, - } } diff --git a/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/hosted/layeredimage/LayeredCompilationSupport.java b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/hosted/layeredimage/LayeredCompilationSupport.java new file mode 100644 index 000000000000..707257356197 --- /dev/null +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/hosted/layeredimage/LayeredCompilationSupport.java @@ -0,0 +1,53 @@ +/* + * 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.common.hosted.layeredimage; + +import java.lang.reflect.Executable; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior.Behavior; + +/** + * Used to programmatically associate {@link LayeredCompilationBehavior} to a method. + */ +@Platforms(Platform.HOSTED_ONLY.class) +public abstract class LayeredCompilationSupport { + + public static LayeredCompilationSupport singleton() { + return ImageSingletons.lookup(LayeredCompilationSupport.class); + } + + /** + * Registers an explicit {@link LayeredCompilationBehavior} to a method. Note it is illegal to + * pass the compilation behavior {@link Behavior#DEFAULT} via this method. In addition, this + * method should be exclusively called by {@link Feature}s in {@link Feature#duringSetup}. + */ + public abstract void registerCompilationBehavior(Executable method, LayeredCompilationBehavior.Behavior behavior); +} 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 new file mode 100644 index 000000000000..054fb1eca486 --- /dev/null +++ b/substratevm/src/com.oracle.svm.common/src/com/oracle/svm/common/layeredimage/LayeredCompilationBehavior.java @@ -0,0 +1,75 @@ +/* + * 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.common.layeredimage; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to specify how a method needs to be compiled when building layered images. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface LayeredCompilationBehavior { + /** + * This state represents how a method should be compiled in layered images. The state of a + * method can only be decided in the first layer if it is marked as tracked across layers. The + * state has to stay the same across all the extension layers. If not specified, the state of a + * method will be {@link Behavior#DEFAULT}. + */ + enum Behavior { + + /** + * Method remains unanalyzed until the application layer and any inlining in a shared layer + * is prevented. A call to the method in a shared layer will be replaced by an indirect + * call. The compilation of those methods is then forced in the application layer and the + * corresponding symbol is declared as global. + * + * A delayed method that is not referenced in any shared layer is treated as a + * {@link Behavior#DEFAULT} method in the application layer and does not have to be + * compiled. If it is only referenced in the application layer, it might be inlined and not + * compiled at all. + */ + FULLY_DELAYED_TO_APPLICATION_LAYER, + + /** + * Method can be inlined into other methods, both before analysis and during compilation, + * and will be compiled as a distinct compilation unit as stipulated by the normal native + * image generation process (i.e., the method is installed as a root and/or a reference to + * the method exists via a call and/or an explicit MethodReference). + */ + DEFAULT, + + /** + * Method is pinned to the initial layer, meaning it has to be analyzed and compiled in this + * specific layer. + */ + PINNED_TO_INITIAL_LAYER, + } + + Behavior value(); +} 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 9c72181f4111..3c112cefd8c4 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 @@ -55,6 +55,7 @@ import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior; import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.c.CGlobalDataFactory; import com.oracle.svm.core.c.function.CEntryPointActions; @@ -165,6 +166,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) public static void invokeMain(String[] args) throws Throwable { String[] mainArgs = args; if (ImageSingletons.contains(PreMainSupport.class)) { @@ -285,7 +287,9 @@ 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) 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/VMInspectionOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java index 97beff946992..a939b7783963 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java @@ -36,6 +36,7 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.impl.InternalPlatform.WINDOWS_BASE; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior; import com.oracle.svm.core.heap.dump.HeapDumping; import com.oracle.svm.core.jdk.management.ManagementAgentModule; import com.oracle.svm.core.option.APIOption; @@ -173,6 +174,7 @@ public static boolean hasHeapDumpSupport() { * the app layer. Otherwise {@link SubstrateOptions#Name} will refer to the initial layer's * name. */ + @LayeredCompilationBehavior(LayeredCompilationBehavior.Behavior.FULLY_DELAYED_TO_APPLICATION_LAYER) static String determineHeapDumpPath() { return HeapDumping.getHeapDumpPath(SubstrateOptions.Name.getValue() + ".hprof"); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java index 8bd57b549710..a9c2bb4a22f4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java @@ -68,7 +68,6 @@ import com.oracle.svm.core.traits.SingletonTraits; import com.oracle.svm.core.util.ByteArrayReader; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.FeatureImpl.AfterCompilationAccessImpl; import com.oracle.svm.util.ReflectionUtil; @@ -116,10 +115,6 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { if (VMInspectionOptions.hasHeapDumpSupport()) { RuntimeSupport.getRuntimeSupport().addStartupHook(new HeapDumpStartupHook()); RuntimeSupport.getRuntimeSupport().addShutdownHook(new HeapDumpShutdownHook()); - if (ImageLayerBuildingSupport.buildingImageLayer()) { - var method = ReflectionUtil.lookupMethod(VMInspectionOptions.class, "determineHeapDumpPath"); - ((FeatureImpl.BeforeAnalysisAccessImpl) access).getMetaAccess().lookupJavaMethod(method).setFullyDelayedToApplicationLayer(); - } } } 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 1407ba6a7934..656815a03de3 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 @@ -24,15 +24,14 @@ */ package com.oracle.svm.hosted.imagelayer; -import org.graalvm.nativeimage.c.type.CCharPointerPointer; +import static com.oracle.svm.common.layeredimage.LayeredCompilationBehavior.Behavior.PINNED_TO_INITIAL_LAYER; + import org.graalvm.nativeimage.hosted.Feature; -import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; -import com.oracle.svm.core.JavaMainWrapper; +import com.oracle.svm.common.hosted.layeredimage.LayeredCompilationSupport; 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; @@ -45,30 +44,19 @@ public boolean isInConfiguration(Feature.IsInConfigurationAccess access) { } @Override - public void beforeAnalysis(BeforeAnalysisAccess a) { - BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) a; - + public void duringSetup(DuringSetupAccess access) { /* * 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); - - /* SVM start-up logic should be pinned to the initial layer. */ - metaAccess.lookupJavaMethod(ReflectionUtil.lookupMethod(JavaMainWrapper.class, "doRun", int.class, CCharPointerPointer.class)).setPinnedToInitialLayer(pinReason); - /* - * We want the method handle invocation present in this method to be inlined, and that is - * only possible within the application layer. - */ - metaAccess.lookupJavaMethod(ReflectionUtil.lookupMethod(JavaMainWrapper.class, "invokeMain", String[].class)).setFullyDelayedToApplicationLayer(); + var compilationSupport = LayeredCompilationSupport.singleton(); + compilationSupport.registerCompilationBehavior(ReflectionUtil.lookupMethod(Unsafe.class, "getUnsafe"), PINNED_TO_INITIAL_LAYER); + compilationSupport.registerCompilationBehavior(ReflectionUtil.lookupMethod(Unsafe.class, "allocateInstance", Class.class), PINNED_TO_INITIAL_LAYER); + 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); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredCompilationSupportFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredCompilationSupportFeature.java new file mode 100644 index 000000000000..9b710e3add42 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredCompilationSupportFeature.java @@ -0,0 +1,99 @@ +/* + * 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 java.lang.reflect.Executable; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.svm.common.hosted.layeredimage.LayeredCompilationSupport; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior; +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.util.UserError; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl; + +/** + * Internal logic for registering methods' layered compilation behavior during the + * {@link Feature#beforeAnalysis} phase. It is expected that the user has invoked all + * {@link LayeredCompilationSupport#registerCompilationBehavior}s in {@link Feature#duringSetup}. + */ +@AutomaticallyRegisteredFeature +public class LayeredCompilationSupportFeature extends LayeredCompilationSupport implements InternalFeature { + record BehaviorRequest(Executable method, LayeredCompilationBehavior.Behavior behavior) { + } + + /** + * Saves the {@link #registerCompilationBehavior} registrations so that they can be registered + * by this feature in the {@link Feature#beforeAnalysis} phase. + */ + Set queueRegistrations = ConcurrentHashMap.newKeySet(); + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return ImageLayerBuildingSupport.buildingImageLayer(); + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + ImageSingletons.add(LayeredCompilationSupport.class, this); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + AnalysisMetaAccess metaAccess = ((FeatureImpl.BeforeAnalysisAccessImpl) access).getMetaAccess(); + for (var registration : queueRegistrations) { + AnalysisMethod aMethod = metaAccess.lookupJavaMethod(registration.method); + registerCompilationBehavior(aMethod, registration.behavior); + } + queueRegistrations = null; + } + + @Override + public void registerCompilationBehavior(Executable method, LayeredCompilationBehavior.Behavior behavior) { + if (queueRegistrations == null) { + throw UserError.abort("Trying to register compilation behavior too late"); + } + if (behavior == LayeredCompilationBehavior.Behavior.DEFAULT) { + throw UserError.abort("Invalid behavior specified %s: %s", behavior, method); + } + queueRegistrations.add(new BehaviorRequest(method, behavior)); + } + + private static void registerCompilationBehavior(AnalysisMethod aMethod, LayeredCompilationBehavior.Behavior behavior) { + switch (behavior) { + case FULLY_DELAYED_TO_APPLICATION_LAYER -> aMethod.setFullyDelayedToApplicationLayer(); + case PINNED_TO_INITIAL_LAYER -> aMethod.setPinnedToInitialLayer(); + default -> throw VMError.shouldNotReachHere("Invalid behavior specified %s: %s", behavior, aMethod); + } + } +} 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 1ea841b6c46b..3b51fa2f2b41 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 @@ -86,6 +86,7 @@ import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.graal.pointsto.util.AnalysisFuture; import com.oracle.graal.pointsto.util.CompletionExecutor.DebugContextRunnable; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.classinitialization.ClassInitializationInfo; @@ -1030,7 +1031,7 @@ public void addBaseLayerMethod(AnalysisMethod analysisMethod) { registerFlag(md.getIsImplementationInvoked(), _ -> analysisMethod.registerAsImplementationInvoked(PERSISTED)); registerFlag(md.getIsIntrinsicMethod(), _ -> analysisMethod.registerAsIntrinsicMethod(PERSISTED)); - AnalysisMethod.CompilationBehavior compilationBehavior = AnalysisMethod.CompilationBehavior.values()[md.getCompilationBehaviorOrdinal()]; + LayeredCompilationBehavior.Behavior compilationBehavior = LayeredCompilationBehavior.Behavior.values()[md.getCompilationBehaviorOrdinal()]; analysisMethod.setCompilationBehavior(compilationBehavior); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java index 74ef2004b5cc..73a7252045f1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java @@ -94,6 +94,7 @@ import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.graal.pointsto.util.AnalysisFuture; +import com.oracle.svm.common.layeredimage.LayeredCompilationBehavior; import com.oracle.svm.core.FunctionPointerHolder; import com.oracle.svm.core.StaticFieldsSupport; import com.oracle.svm.core.SubstrateOptions; @@ -102,6 +103,7 @@ import com.oracle.svm.core.classinitialization.ClassInitializationInfo; import com.oracle.svm.core.graal.code.CGlobalDataBasePointer; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.common.hosted.layeredimage.LayeredCompilationSupport; import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; @@ -116,6 +118,7 @@ import com.oracle.svm.core.traits.SingletonLayeredCallbacks; import com.oracle.svm.core.traits.SingletonLayeredInstallationKind; import com.oracle.svm.core.traits.SingletonTraitKind; +import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageSingletonsSupportImpl; import com.oracle.svm.hosted.SVMHost; @@ -632,7 +635,12 @@ private void persistMethod(AnalysisMethod method, Supplier".equals(methodName)) { + methodName = enclosingElement.getSimpleName().toString(); + } + fileName.append(METHOD_NAME_COMPONENT_SEPARATOR).append(methodName).append(METHOD_NAME_COMPONENT_SEPARATOR).append(getDescriptorForClass(e.getReturnType())); + + var parameters = e.getParameters(); + if (!parameters.isEmpty()) { + fileName.append(METHOD_NAME_COMPONENT_SEPARATOR); + for (var parameter : parameters) { + fileName.append(getDescriptorForClass(parameter.asType())); + } + } + + return fileName.toString(); + } + + private static String computeAnnotatedMethodName(ExecutableElement e) { + TypeElement enclosingElement = (TypeElement) e.getEnclosingElement(); + return enclosingElement.getQualifiedName().toString() + "#" + e.getSimpleName().toString(); + } + + private static String computeDeclaringClass(ExecutableElement e) { + TypeElement enclosingElement = (TypeElement) e.getEnclosingElement(); + return enclosingElement.getQualifiedName().toString() + ".class"; + } + + 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)); + if (!params.isEmpty()) { + return ", " + params; + } + return ""; + } + + private static String getDescriptorForClass(TypeMirror c) { + return switch (c.getKind()) { + case BOOLEAN -> "Z"; + case BYTE -> "B"; + case CHAR -> "C"; + case SHORT -> "S"; + case INT -> "I"; + case LONG -> "J"; + case FLOAT -> "F"; + case DOUBLE -> "D"; + case VOID -> "V"; + case ARRAY -> ARRAY_IDENTIFIER + getDescriptorForClass(((ArrayType) c).getComponentType()); + case DECLARED -> "L" + c.toString().replace('.', OBJECT_PATH_SEPARATOR); + default -> throw new RuntimeException("Unexpected null type: " + c); + }; + } + + @Override + protected boolean doProcess(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + return true; + } + + for (Element element : roundEnv.getElementsAnnotatedWith(getTypeElement(ANNOTATION_CLASS_NAME))) { + assert element.getKind().isExecutable(); + processElement((ExecutableElement) element); + } + return true; + } +}