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);