diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingleton.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingleton.java
index 7a4961a61d50..e1c196b21477 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingleton.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/LayeredImageSingleton.java
@@ -30,6 +30,8 @@
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
+import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport;
+
/**
* In additional to the traditional singleton model, i.e. a key-value map whose lookups are constant
* folded within generated code, we provide two additional options:
@@ -42,12 +44,13 @@
*
{@link MultiLayeredImageSingleton}: {@link ImageSingletons#lookup} calls continue to refer to
* the appropriate per layer image singleton, but there is also an additional method
* {@link MultiLayeredImageSingleton#getAllLayers} which returns an array with the image singletons
- * corresponding to this key in all layers they were created. The length of this array will vary
- * from [0, total #layers], based on the number of layers singletons were installed in (i.e., it is
- * not required for the singleton to be installed in all layers). Within the array, the singletons
- * will be arranged so that index [0] corresponds to the singleton originating from the oldest layer
- * in which the singleton was installed and index [length - 1] holds the singleton from the newest
- * layer.
+ * corresponding to this key for all layers. The length of this array will always be the total
+ * number of layers. If a singleton corresponding to this key was not installed in a given layer
+ * (and this is allowed), then the array will contain null for the given index. See
+ * {@link MultiLayeredAllowNullEntries} for more details. Within the array, the singletons will be
+ * arranged so that index [0] corresponds to the singleton originating from the initial layer and
+ * index [length - 1] holds the singleton from the application layer. See
+ * {@link ImageLayerBuildingSupport} for a description of the different layer names.
*
*
* Note the unique behavior of {@link ApplicationLayerOnlyImageSingleton} applies only when building
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/MultiLayeredAllowNullEntries.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/MultiLayeredAllowNullEntries.java
new file mode 100644
index 000000000000..d48e9aa1f72f
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/MultiLayeredAllowNullEntries.java
@@ -0,0 +1,43 @@
+/*
+ * 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.core.layeredimagesingleton;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * By default, it is expected a given {@link MultiLayeredImageSingleton} has a singleton installed
+ * in each layer. If instead you want to make installation optional, you must add this annotation to
+ * your class.
+ *
+ * Note this annotation is only meaningful when applied to {@link MultiLayeredImageSingleton}. In
+ * all other scenarios it is ignored.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface MultiLayeredAllowNullEntries {
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/MultiLayeredImageSingleton.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/MultiLayeredImageSingleton.java
index b27605f8b3f2..f70f14d06ca1 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/MultiLayeredImageSingleton.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/layeredimagesingleton/MultiLayeredImageSingleton.java
@@ -41,9 +41,9 @@ static T[] getAllLayers(Class key) {
}
/**
- * Retrieve a specific layer from a MultiLayeredImageSingleton. Note if a
- * MultiLayeredImageSingleton is not installed in all layers, then the singletons index will not
- * match the layer number it was installed in.
+ * Retrieve a specific layer's singleton from a MultiLayeredImageSingleton. The index represents
+ * which layer number's singleton to retrieve. If a singleton was not installed in that layer
+ * (and this is allowed), then null is returned.
*/
@SuppressWarnings("unused")
static T getForLayer(Class key, int index) {
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadImageSingletonFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadImageSingletonFeature.java
index 853fa32e6cdc..5e960747628e 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadImageSingletonFeature.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadImageSingletonFeature.java
@@ -35,10 +35,11 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
-import java.util.stream.Stream;
+import org.graalvm.nativeimage.AnnotationAccess;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.word.Pointer;
@@ -54,6 +55,7 @@
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
import com.oracle.svm.core.graal.code.CGlobalDataInfo;
+import com.oracle.svm.core.imagelayer.DynamicImageLayerInfo;
import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport;
import com.oracle.svm.core.imagelayer.LoadImageSingletonFactory;
import com.oracle.svm.core.layeredimagesingleton.ApplicationLayerOnlyImageSingleton;
@@ -63,8 +65,10 @@
import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton;
import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags;
import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonSupport;
+import com.oracle.svm.core.layeredimagesingleton.MultiLayeredAllowNullEntries;
import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton;
import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton;
+import com.oracle.svm.core.util.UserError;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.FeatureImpl;
import com.oracle.svm.hosted.c.CGlobalDataFeature;
@@ -74,6 +78,7 @@
import com.oracle.svm.util.ReflectionUtil;
import jdk.graal.compiler.api.replacements.SnippetReflectionProvider;
+import jdk.graal.compiler.debug.GraalError;
import jdk.graal.compiler.nodes.ConstantNode;
import jdk.graal.compiler.nodes.ValueNode;
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
@@ -166,6 +171,12 @@ public void duringSetup(DuringSetupAccess access) {
LayeredImageHeapObjectAdder.singleton().registerObjectAdder(this::addInitialObjects);
}
+ static void checkAllowNullEntries(Class> key) {
+ boolean nullEntriesAllowed = AnnotationAccess.isAnnotationPresent(key, MultiLayeredAllowNullEntries.class);
+ UserError.guarantee(nullEntriesAllowed,
+ "This MultiLayeredSingleton requires an entry to be installed in every layer. Please see the javadoc within MultiLayeredAllowNullEntries for more details.");
+ }
+
/**
* This method needs to be called after all image singletons are registered. Currently, some
* singletons are registered in
@@ -229,6 +240,8 @@ public void processRegisteredSingletons(AnalysisUniverse universe) {
Class> key = slotInfo.keyClass();
if (ImageSingletons.contains(key)) {
multiLayerEmbeddedRoots.add(layeredImageSingletonSupport.runtimeLookup(key));
+ } else {
+ checkAllowNullEntries(key);
}
/*
* Within the application layer there will be an array created to hold all
@@ -262,6 +275,9 @@ public void processRegisteredSingletons(AnalysisUniverse universe) {
if (!multiLayerEmbeddedRoots.isEmpty()) {
multiLayerEmbeddedRootsRegistration.accept(multiLayerEmbeddedRoots.toArray());
}
+ } else {
+ // GR-58631
+ throw GraalError.unimplemented("More work needed for 3+ layer support");
}
}
@@ -273,15 +289,46 @@ public void beforeCompilation(BeforeCompilationAccess access) {
ImageHeapObjectArray createMultiLayerArray(Class> key, AnalysisType arrayType, SnippetReflectionProvider snippetReflectionProvider) {
List priorIds = getCrossLayerSingletonMappingInfo().getPriorLayerObjectIDs(key);
- Stream values = priorIds.stream().map(priorId -> loader.getOrCreateConstant(priorId));
+ var layerInfo = DynamicImageLayerInfo.singleton();
+
+ final JavaConstant[] elements = new JavaConstant[layerInfo.numLayers];
+ BiConsumer installElement = (priorConstant, index) -> {
+ assert elements[index] == null : elements[index];
+ elements[index] = priorConstant;
+ };
+ // first install prior singletons
+ priorIds.forEach(priorId -> {
+ var priorConstant = loader.getOrCreateConstant(priorId);
+ int index = 0;
+ assert layerInfo.numLayers == 2 : "incompatible with 3+ layers";
+ installElement.accept(priorConstant, index);
+ });
+
+ // next install current singleton
if (ImageSingletons.contains(key)) {
var singleton = LayeredImageSingletonSupport.singleton().runtimeLookup(key);
JavaConstant singletonConstant = snippetReflectionProvider.forObject(singleton);
- values = Stream.concat(values, Stream.of(singletonConstant));
+ installElement.accept(singletonConstant, layerInfo.layerNumber);
+ }
+
+ // finally fill any missing holes
+ boolean holesPresent = false;
+ for (int i = 0; i < layerInfo.numLayers; i++) {
+ if (elements[i] == null) {
+ holesPresent = true;
+ installElement.accept(JavaConstant.NULL_POINTER, i);
+ }
+ }
+
+ if (holesPresent) {
+ /*
+ * Once more validate that null entries are allowed. However, any issues are expected to
+ * be caught before this point.
+ */
+ checkAllowNullEntries(key);
}
- Object[] elements = values.toArray();
return ImageHeapObjectArray.createUnbackedImageHeapArray(arrayType, elements);
}
@@ -477,6 +524,15 @@ private LoadImageSingletonData getImageSingletonInfo(Class> keyClass, SlotReco
return result;
}
+ if (result.kind == SlotRecordKind.MULTI_LAYERED_SINGLETON) {
+ if (!ImageSingletons.contains(keyClass)) {
+ /*
+ * If the singleton doesn't exist, ensure this is allowed.
+ */
+ LoadImageSingletonFeature.checkAllowNullEntries(keyClass);
+ }
+ }
+
SlotInfo priorSlotInfo = priorKeyToSlotInfoMap.get(keyClass);
if (priorSlotInfo != null && priorSlotInfo.recordKind() != kind) {
VMError.shouldNotReachHere("A singleton cannot implement both %s and %s", priorSlotInfo.recordKind(), kind);