From 2270dceff488aa8fbc88a6c104bb7937f8df9e46 Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Mon, 8 Sep 2025 14:46:05 +0200 Subject: [PATCH] Make IsolateArgumentParser layer-aware. --- .../graal/pointsto/meta/AnalysisMethod.java | 4 + .../svm/core/IsolateArgumentParser.java | 116 +++++++++++++++++- .../com/oracle/svm/core/JavaMainWrapper.java | 4 + .../oracle/svm/core/VMInspectionOptions.java | 11 +- .../hosted/ImageSingletonsSupportImpl.java | 3 + .../svm/hosted/heap/HeapDumpFeature.java | 5 + .../HostedImageLayerBuildingSupport.java | 1 - .../imagelayer/InitialLayerFeature.java | 12 +- .../hosted/option/RuntimeOptionFeature.java | 28 +++-- 9 files changed, 168 insertions(+), 16 deletions(-) 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 6f6d212ca929..67210181eaa0 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,6 +353,10 @@ public boolean isDelayed() { return compilationBehavior == CompilationBehavior.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. + */ 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); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java index 24de917ab0ed..6408277806fb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java @@ -34,7 +34,9 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.List; +import java.util.Objects; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -53,12 +55,28 @@ import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.graal.RuntimeCompilation; import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.imagelayer.BuildingImageLayerPredicate; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; import com.oracle.svm.core.memory.UntrackedNullableNativeMemory; import com.oracle.svm.core.option.RuntimeOptionKey; +import com.oracle.svm.core.traits.BuiltinTraits.AllAccess; +import com.oracle.svm.core.traits.BuiltinTraits.BuildtimeAccessOnly; +import com.oracle.svm.core.traits.BuiltinTraits.SingleLayer; +import com.oracle.svm.core.traits.SingletonLayeredCallbacks; +import com.oracle.svm.core.traits.SingletonLayeredCallbacksSupplier; +import com.oracle.svm.core.traits.SingletonLayeredInstallationKind.Independent; +import com.oracle.svm.core.traits.SingletonLayeredInstallationKind.InitialLayerOnly; +import com.oracle.svm.core.traits.SingletonTrait; +import com.oracle.svm.core.traits.SingletonTraitKind; +import com.oracle.svm.core.traits.SingletonTraits; import com.oracle.svm.core.util.ImageHeapList; import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.word.Word; /** @@ -69,6 +87,7 @@ * stored in {@code argv} is used. */ @AutomaticallyRegisteredImageSingleton +@SingletonTraits(access = AllAccess.class, layeredCallbacks = SingleLayer.class, layeredInstallationKind = InitialLayerOnly.class) public class IsolateArgumentParser { @SuppressWarnings("unchecked")// private final List> options = (List>) ImageHeapList.createGeneric(RuntimeOptionKey.class); @@ -183,7 +202,11 @@ protected static List> getOptions() { @Fold protected static int getOptionCount() { - return getOptions().size(); + if (ImageLayerBuildingSupport.firstImageBuild()) { + return getOptions().size(); + } else { + return LayeredOptionInfo.singleton().getNumOptions(); + } } @Uninterruptible(reason = "Still being initialized.") @@ -559,10 +582,20 @@ private static CCharPointer startsWith(CCharPointer input, CCharPointer prefix) @Fold public static int getOptionIndex(RuntimeOptionKey key) { - List> options = getOptions(); - for (int i = 0; i < options.size(); i++) { - if (options.get(i) == key) { - return i; + if (ImageLayerBuildingSupport.firstImageBuild()) { + List> options = getOptions(); + for (int i = 0; i < options.size(); i++) { + if (options.get(i) == key) { + return i; + } + } + } else { + var keyName = key.getName(); + var optionNames = LayeredOptionInfo.singleton().getOptionNames(); + for (int i = 0; i < optionNames.size(); i++) { + if (optionNames.get(i).equals(keyName)) { + return i; + } } } @@ -599,4 +632,77 @@ public static boolean isNumeric(byte optionValueType) { return optionValueType == INTEGER || optionValueType == LONG; } } + + /** + * Within {@link IsolateArgumentParser} many methods need to be {@link Fold}ed. This class adds + * support so that we can handle these method folds within the application layer. + */ + @Platforms(Platform.HOSTED_ONLY.class) + @AutomaticallyRegisteredImageSingleton(onlyWith = BuildingImageLayerPredicate.class) + @SingletonTraits(access = BuildtimeAccessOnly.class, layeredCallbacks = LayeredCallbacks.class, layeredInstallationKind = Independent.class) + static class LayeredOptionInfo { + private static final int UNSET = -1; + final int numOptions; + final List optionNames; + + LayeredOptionInfo() { + this(UNSET, null); + } + + LayeredOptionInfo(int numOptions, List optionNames) { + this.numOptions = numOptions; + this.optionNames = optionNames; + } + + static LayeredOptionInfo singleton() { + return ImageSingletons.lookup(LayeredOptionInfo.class); + } + + int getNumOptions() { + if (numOptions == UNSET) { + throw VMError.shouldNotReachHere("numOptions is unset"); + } + return numOptions; + } + + List getOptionNames() { + Objects.requireNonNull(optionNames); + return optionNames; + } + } + + static class LayeredCallbacks extends SingletonLayeredCallbacksSupplier { + + @Override + public SingletonTrait getLayeredCallbacksTrait() { + return new SingletonTrait(SingletonTraitKind.LAYERED_CALLBACKS, new SingletonLayeredCallbacks() { + @Override + public LayeredImageSingleton.PersistFlags doPersist(ImageSingletonWriter writer, Object singleton) { + if (ImageLayerBuildingSupport.firstImageBuild()) { + writer.writeInt("numOptions", IsolateArgumentParser.getOptionCount()); + writer.writeStringList("optionNames", IsolateArgumentParser.getOptions().stream().map(OptionKey::getName).toList()); + } else { + var metadata = (LayeredOptionInfo) singleton; + writer.writeInt("numOptions", metadata.getNumOptions()); + writer.writeStringList("optionNames", metadata.optionNames); + } + return LayeredImageSingleton.PersistFlags.CREATE; + } + + @Override + public Class getSingletonInstantiator() { + return SingletonInstantiator.class; + } + }); + } + } + + static class SingletonInstantiator implements SingletonLayeredCallbacks.LayeredSingletonInstantiator { + @Override + public Object createFromLoader(ImageSingletonLoader loader) { + int numOptions = loader.readInt("numOptions"); + var optionNames = Collections.unmodifiableList(loader.readStringList("optionNames")); + return new LayeredOptionInfo(numOptions, optionNames); + } + } } 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 f22c8bbdde73..9c72181f4111 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 @@ -161,6 +161,10 @@ 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. + */ public static void invokeMain(String[] args) throws Throwable { String[] mainArgs = args; if (ImageSingletons.contains(PreMainSupport.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 67f563558226..97beff946992 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 @@ -168,9 +168,18 @@ public static boolean hasHeapDumpSupport() { return hasAllOrKeywordMonitoringSupport(MONITORING_HEAPDUMP_NAME) && !Platform.includedIn(WINDOWS_BASE.class); } + /** + * This needs to be an explicit method so that in layered builds compilation can be deferred to + * the app layer. Otherwise {@link SubstrateOptions#Name} will refer to the initial layer's + * name. + */ + static String determineHeapDumpPath() { + return HeapDumping.getHeapDumpPath(SubstrateOptions.Name.getValue() + ".hprof"); + } + public static boolean dumpImageHeap() { if (hasHeapDumpSupport()) { - String absoluteHeapDumpPath = HeapDumping.getHeapDumpPath(SubstrateOptions.Name.getValue() + ".hprof"); + String absoluteHeapDumpPath = determineHeapDumpPath(); try { HeapDumping.singleton().dumpHeap(absoluteHeapDumpPath, true); } catch (IOException e) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java index 7c6f57c325a5..7fb733becaa1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageSingletonsSupportImpl.java @@ -617,6 +617,9 @@ T doLookup(Class key, boolean buildtimeAccess, boolean allowMultiLayered) VMError.guarantee(info.singleton() != null); Object singleton = info.singleton(); + if (singleton == SINGLETON_INSTALLATION_FORBIDDEN) { + throw UserError.abort("Singleton is forbidden in current layer. Key: %s", key.getTypeName()); + } if (!allowMultiLayered) { var trait = info.traitMap().getTrait(SingletonTraitKind.LAYERED_INSTALLATION_KIND); trait.ifPresent(t -> { 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 99987cb40ea8..69e52fae31cf 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 @@ -63,6 +63,7 @@ import com.oracle.svm.core.meta.SharedType; 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; @@ -110,6 +111,10 @@ 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/HostedImageLayerBuildingSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java index 42b229703ace..f34ca5c9dd98 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 @@ -245,7 +245,6 @@ public static void processLayerOptions(EconomicMap, Object> values, if (isLayerUseOptionEnabled(hostedOptions)) { SubstrateOptions.ClosedTypeWorldHubLayout.update(values, false); - SubstrateOptions.ParseRuntimeOptions.update(values, false); if (SubstrateOptions.imageLayerEnabledHandler != null) { SubstrateOptions.imageLayerEnabledHandler.onOptionEnabled(values); } 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 9a228796d93f..1407ba6a7934 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,9 +24,11 @@ */ package com.oracle.svm.hosted.imagelayer; -import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import org.graalvm.nativeimage.c.type.CCharPointerPointer; import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.svm.core.JavaMainWrapper; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; @@ -59,6 +61,14 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { 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(); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/option/RuntimeOptionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/option/RuntimeOptionFeature.java index f6c37112611d..b76a55c5881c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/option/RuntimeOptionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/option/RuntimeOptionFeature.java @@ -34,6 +34,7 @@ import com.oracle.svm.core.IsolateArgumentParser; 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.option.HostedOptionKey; import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.core.option.RuntimeOptionKey; @@ -65,20 +66,31 @@ public void duringSetup(DuringSetupAccess a) { public void beforeAnalysis(BeforeAnalysisAccess access) { FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; + boolean extensionLayer = ImageLayerBuildingSupport.buildingExtensionLayer(); UnmodifiableEconomicMap, Object> map = HostedOptionValues.singleton().getMap(); for (OptionKey key : map.getKeys()) { if (key instanceof RuntimeOptionKey runtimeOptionKey && runtimeOptionKey.shouldRegisterForIsolateArgumentParser()) { - /* - * The list of options IsolateArgumentParser has to parse, is built dynamically, to - * include only options of the current configuration. Here, all options that should - * get parsed by the IsolateArgumentParser are added to this list. - */ - IsolateArgumentParser.singleton().register(runtimeOptionKey); - registerOptionAsRead(accessImpl, runtimeOptionKey.getDescriptor().getDeclaringClass(), runtimeOptionKey.getName()); + if (!extensionLayer) { + /* + * The list of options IsolateArgumentParser has to parse, is built dynamically, + * to include only options of the current configuration. Here, all options that + * should get parsed by the IsolateArgumentParser are added to this list. + */ + IsolateArgumentParser.singleton().register(runtimeOptionKey); + registerOptionAsRead(accessImpl, runtimeOptionKey.getDescriptor().getDeclaringClass(), runtimeOptionKey.getName()); + } else { + /* + * All runtime options must have already been installed within the base layer. + * Within the extension layer we only confirm they are present. + */ + assert IsolateArgumentParser.getOptionIndex(runtimeOptionKey) >= 0; + } } } - IsolateArgumentParser.singleton().sealOptions(); + if (!extensionLayer) { + IsolateArgumentParser.singleton().sealOptions(); + } } @SuppressWarnings("unused")