diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CGlobalDataNonConstantRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CGlobalDataNonConstantRegistry.java index 2c544d2a29f3..364e6c456496 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CGlobalDataNonConstantRegistry.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/CGlobalDataNonConstantRegistry.java @@ -43,7 +43,7 @@ */ public class CGlobalDataNonConstantRegistry { - private final EconomicMap, CGlobalDataInfo> cGlobalDataInfos = ImageHeapMap.create(Equivalence.IDENTITY); + private final EconomicMap, CGlobalDataInfo> cGlobalDataInfos = ImageHeapMap.create(Equivalence.IDENTITY, "cGlobalDataInfos"); @Platforms(Platform.HOSTED_ONLY.class) // private final Lock lock = new ReentrantLock(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Pod.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Pod.java index 155f91ce4d18..cc67450b46df 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Pod.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Pod.java @@ -332,7 +332,7 @@ public PodInfo(Class podClass, Constructor factoryCtor) { } } - private final EconomicMap pods = ImageHeapMap.create(); + private final EconomicMap pods = ImageHeapMap.create("pods"); @Platforms(Platform.HOSTED_ONLY.class) public RuntimeSupport() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java index 18a05ad417a1..b2008cbaf027 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java @@ -67,11 +67,11 @@ private static ClassForNameSupport[] layeredSingletons() { /** * The map used to collect registered classes. */ - private final EconomicMap> knownClasses = ImageHeapMap.create(); + private final EconomicMap> knownClasses = ImageHeapMap.createNonLayeredMap(); /** * The map used to collect unsafe allocated classes. */ - private final EconomicMap, RuntimeConditionSet> unsafeInstantiatedClasses = ImageHeapMap.create(); + private final EconomicMap, RuntimeConditionSet> unsafeInstantiatedClasses = ImageHeapMap.createNonLayeredMap(); private static final Object NEGATIVE_QUERY = new Object(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/PredefinedClassesSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/PredefinedClassesSupport.java index 27bf72030b3c..924bde6abcdf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/PredefinedClassesSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/PredefinedClassesSupport.java @@ -109,7 +109,7 @@ static PredefinedClassesSupport singleton() { private final ReentrantLock lock = new ReentrantLock(); /** Predefined classes by hash. */ - private final EconomicMap> predefinedClassesByHash = ImageHeapMap.create(); + private final EconomicMap> predefinedClassesByHash = ImageHeapMap.create("predefinedClassesByHash"); /** Predefined classes which have already been loaded, by name. */ private final EconomicMap> loadedClassesByName = EconomicMap.create(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java index 076605a2b065..fd945d1ee996 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java @@ -115,8 +115,8 @@ public static Resources[] layeredSingletons() { * ends up in the image heap is computed after the runtime module instances have been computed * {see com.oracle.svm.hosted.ModuleLayerFeature}. */ - private final EconomicMap> resources = ImageHeapMap.create(); - private final EconomicMap requestedPatterns = ImageHeapMap.create(); + private final EconomicMap> resources = ImageHeapMap.createNonLayeredMap(); + private final EconomicMap requestedPatterns = ImageHeapMap.createNonLayeredMap(); public record RequestedPattern(String module, String resource) { } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java index e70cb02214d9..8d60533b6561 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java @@ -87,7 +87,7 @@ public class LocalizationSupport { public final Charset defaultCharset; - private final EconomicMap registeredBundles = ImageHeapMap.create(); + private final EconomicMap registeredBundles = ImageHeapMap.create("registeredBundles"); public LocalizationSupport(Set locales, Charset defaultCharset) { this.allLocales = locales.toArray(new Locale[0]); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNILibraryInitializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNILibraryInitializer.java index 70f683aae88b..05ee136af116 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNILibraryInitializer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNILibraryInitializer.java @@ -29,7 +29,6 @@ import java.util.Collection; import java.util.List; -import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; import org.graalvm.nativeimage.Platform; @@ -47,6 +46,8 @@ import com.oracle.svm.core.jni.headers.JNIVersion; import com.oracle.svm.core.util.ImageHeapMap; +import jdk.graal.compiler.word.Word; + interface JNIOnLoadFunctionPointer extends CFunctionPointer { @InvokeCFunctionPointer int invoke(JNIJavaVM vm, VoidPointer reserved); @@ -54,7 +55,7 @@ interface JNIOnLoadFunctionPointer extends CFunctionPointer { public class JNILibraryInitializer implements NativeLibrarySupport.LibraryInitializer { - private final EconomicMap> onLoadCGlobalDataMap = ImageHeapMap.create(Equivalence.IDENTITY); + private final EconomicMap> onLoadCGlobalDataMap = ImageHeapMap.create(Equivalence.IDENTITY, "onLoadCGlobalDataMap"); private static String getOnLoadName(String libName, boolean isBuiltIn) { String name = "JNI_OnLoad"; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleClass.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleClass.java index 8f93b1a2dcfa..a7fa6b1b3a94 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleClass.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIAccessibleClass.java @@ -76,7 +76,7 @@ public JNIAccessibleField getField(CharSequence name) { @Platforms(HOSTED_ONLY.class) public void addFieldIfAbsent(String name, Function mappingFunction) { if (fields == null) { - fields = ImageHeapMap.create(JNIReflectionDictionary.WRAPPED_CSTRING_EQUIVALENCE); + fields = ImageHeapMap.createNonLayeredMap(JNIReflectionDictionary.WRAPPED_CSTRING_EQUIVALENCE); } if (!fields.containsKey(name)) { fields.put(name, mappingFunction.apply(name)); @@ -86,7 +86,7 @@ public void addFieldIfAbsent(String name, Function m @Platforms(HOSTED_ONLY.class) public void addMethodIfAbsent(JNIAccessibleMethodDescriptor descriptor, Function mappingFunction) { if (methods == null) { - methods = ImageHeapMap.create(); + methods = ImageHeapMap.createNonLayeredMap(); } if (!methods.containsKey(descriptor)) { methods.put(descriptor, mappingFunction.apply(descriptor)); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java index 73d2ec7563bc..1600a5f7fdda 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java @@ -105,9 +105,9 @@ private static JNIReflectionDictionary[] layeredSingletons() { return MultiLayeredImageSingleton.getAllLayers(JNIReflectionDictionary.class); } - private final EconomicMap classesByName = ImageHeapMap.create(WRAPPED_CSTRING_EQUIVALENCE); - private final EconomicMap, JNIAccessibleClass> classesByClassObject = ImageHeapMap.create(); - private final EconomicMap nativeLinkages = ImageHeapMap.create(); + private final EconomicMap classesByName = ImageHeapMap.createNonLayeredMap(WRAPPED_CSTRING_EQUIVALENCE); + private final EconomicMap, JNIAccessibleClass> classesByClassObject = ImageHeapMap.createNonLayeredMap(); + private final EconomicMap nativeLinkages = ImageHeapMap.createNonLayeredMap(); private JNIReflectionDictionary() { } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java index d2c2cead90db..256f1894e467 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java @@ -105,7 +105,7 @@ public static String[] parseAndConsumeAllOptions(String[] initialArgs, boolean i } /** All reachable options. */ - public EconomicMap options = ImageHeapMap.create(); + public EconomicMap options = ImageHeapMap.create("options"); @Platforms(Platform.HOSTED_ONLY.class) public void addDescriptor(OptionDescriptor optionDescriptor) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java index 43d25b8661c8..75ff23639307 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java @@ -40,6 +40,7 @@ import com.oracle.svm.core.configure.RuntimeConditionSet; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.PredefinedClassesSupport; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import com.oracle.svm.core.util.ImageHeapMap; @@ -73,7 +74,17 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return Arrays.hashCode(interfaces); + if (ImageLayerBuildingSupport.buildingImageLayer()) { + /* + * The hash code cannot be computed using the interfaces' hash code in Layered Image + * because the hash code of classes cannot be injected in the application layer. + * This causes the internal structure of the proxyCache to be unusable in the + * application layer. + */ + return Arrays.hashCode(Arrays.stream(interfaces).map(Class::getName).toArray()); + } else { + return Arrays.hashCode(interfaces); + } } @Override @@ -82,7 +93,7 @@ public String toString() { } } - private final EconomicMap> proxyCache = ImageHeapMap.create(); + private final EconomicMap> proxyCache = ImageHeapMap.create("proxyCache"); @Platforms(Platform.HOSTED_ONLY.class) public DynamicProxySupport() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java index bb3dcc0ad476..d20b8382277a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java @@ -136,7 +136,7 @@ public int hashCode() { @Platforms(Platform.HOSTED_ONLY.class) public SerializationSupport() { - constructorAccessors = ImageHeapMap.create(); + constructorAccessors = ImageHeapMap.create("constructorAccessors"); } public void setStubConstructor(Constructor stubConstructor) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ImageHeapMap.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ImageHeapMap.java index c386677b0f08..69c5d1c36a21 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ImageHeapMap.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ImageHeapMap.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.util; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.graalvm.collections.EconomicMap; @@ -33,6 +34,7 @@ import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; /** * A thread-safe map implementation that optimizes its run time representation for space efficiency. @@ -57,25 +59,80 @@ private ImageHeapMap() { * {@link EconomicMap} backed by a flat object array. */ @Platforms(Platform.HOSTED_ONLY.class) // - public static EconomicMap create() { + public static EconomicMap create(String key) { + return create(Equivalence.DEFAULT, key); + } + + @Platforms(Platform.HOSTED_ONLY.class) // + public static EconomicMap createNonLayeredMap() { + return createNonLayeredMap(Equivalence.DEFAULT); + } + + @Platforms(Platform.HOSTED_ONLY.class) // + public static EconomicMap create(Equivalence strategy, String key) { + assert key != null : "The key should not be null if the map needs to be automatically layered"; VMError.guarantee(!BuildPhaseProvider.isAnalysisFinished(), "Trying to create an ImageHeapMap after analysis."); - return new HostedImageHeapMap<>(Equivalence.DEFAULT); + return HostedImageHeapMap.create(strategy, key, true); } @Platforms(Platform.HOSTED_ONLY.class) // - public static EconomicMap create(Equivalence strategy) { + public static EconomicMap createNonLayeredMap(Equivalence strategy) { VMError.guarantee(!BuildPhaseProvider.isAnalysisFinished(), "Trying to create an ImageHeapMap after analysis."); - return new HostedImageHeapMap<>(strategy); + return HostedImageHeapMap.create(strategy, null, false); } @Platforms(Platform.HOSTED_ONLY.class) // public static final class HostedImageHeapMap extends EconomicMapWrap { + private final EconomicMap currentLayerMap; + private final EconomicMap runtimeMap; - public final EconomicMap runtimeMap; + /** + * The {code key} is only used in the Layered Image context, to link the maps across each + * layer. If an {@link ImageHeapMap} is in a singleton that is already layer aware, there is + * no need to use a {@link LayeredImageHeapMap}, as the singleton should already handle the + * link across layers. In this case, {@code isAlreadyLayered} should be true and the + * {@code key} can be {@code null}. + */ + public HostedImageHeapMap(Map hostedMap, EconomicMap currentLayerMap, EconomicMap runtimeMap) { + super(hostedMap); + this.currentLayerMap = currentLayerMap; + this.runtimeMap = runtimeMap; + } + + @Platforms(Platform.HOSTED_ONLY.class) // + public EconomicMap getCurrentLayerMap() { + return currentLayerMap; + } + + /** + * Returns the run time representation of this map. In a non-layered build this is the same + * as the {@link #currentLayerMap} object returned by {@link #getCurrentLayerMap()}, i.e., + * an {@link EconomicMap}. In a layered build this is a special {@link LayeredImageHeapMap} + * object that retrieves the layered key-value pairs by accessing the + * {@link #currentLayerMap} installed in every layer. + */ + public EconomicMap getRuntimeMap() { + return runtimeMap; + } - HostedImageHeapMap(Equivalence strategy) { - super((strategy == Equivalence.IDENTITY) ? new ConcurrentIdentityHashMap<>() : new ConcurrentHashMap<>()); - this.runtimeMap = EconomicMap.create(strategy); + public static HostedImageHeapMap create(Equivalence strategy, String key, boolean needLayeredMap) { + Map hostedMap = (strategy == Equivalence.IDENTITY) ? new ConcurrentIdentityHashMap<>() : new ConcurrentHashMap<>(); + EconomicMap currentLayerMap = EconomicMap.create(strategy); + if (!needLayeredMap || !ImageLayerBuildingSupport.buildingImageLayer()) { + return new HostedImageHeapMap<>(hostedMap, currentLayerMap, currentLayerMap); + } else { + LayeredImageHeapMap runtimeMap = new LayeredImageHeapMap<>(strategy, key); + var previousMap = LayeredImageHeapMapStore.currentLayer().getImageHeapMapStore().put(key, currentLayerMap); + if (previousMap != null) { + throw VMError.shouldNotReachHere("The LayeredImageHeapMap with key %s was added twice", key); + } + HostedImageHeapMap hostedImageHeapMap = new HostedImageHeapMap<>(hostedMap, currentLayerMap, runtimeMap); + LayeredHostedImageHeapMapCollector singleton = LayeredHostedImageHeapMapCollector.singleton(); + if (ImageLayerBuildingSupport.buildingExtensionLayer() && singleton.isMapKeyReachableInPreviousLayer(key)) { + singleton.registerPreviousLayerHostedImageHeapMap(hostedImageHeapMap); + } + return hostedImageHeapMap; + } } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/LayeredHostedImageHeapMapCollector.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/LayeredHostedImageHeapMapCollector.java new file mode 100644 index 000000000000..a584de6accbc --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/LayeredHostedImageHeapMapCollector.java @@ -0,0 +1,115 @@ +/* + * 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.util; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +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.layeredimagesingleton.LayeredImageSingletonBuilderFlags; +import com.oracle.svm.core.util.ImageHeapMap.HostedImageHeapMap; + +/** + * {@link HostedImageHeapMap} are stored in {@code ImageHeapCollectionFeature#allMaps} through an + * object replacer, meaning that only maps reachable at run time are tracked and rescanned. In the + * extension layers, some maps can be extended at build time, but not be reachable from run time + * code of the current layer. So all the maps reachable in the base layer need to be tracked and + * when the map is created in an extension layer, it needs to be manually added to + * {@code ImageHeapCollectionFeature#allMaps} to ensure it is always rescanned and reachable. + */ +@Platforms(Platform.HOSTED_ONLY.class) +public class LayeredHostedImageHeapMapCollector implements LayeredImageSingleton { + /** + * Map keys of maps reachable in the current layer. + */ + private final List currentLayerReachableMapsKeys = new ArrayList<>(); + /** + * Map keys of maps reachable in the previous layers. + */ + private final List previousLayerReachableMapKeys; + /** + * Maps reachable in the previous layers. + */ + private final List> previousLayerReachableMaps = ImageLayerBuildingSupport.buildingExtensionLayer() ? new ArrayList<>() : null; + + public LayeredHostedImageHeapMapCollector() { + this(null); + } + + private LayeredHostedImageHeapMapCollector(List previousLayerReachableMapKeys) { + this.previousLayerReachableMapKeys = previousLayerReachableMapKeys; + } + + public static LayeredHostedImageHeapMapCollector singleton() { + return ImageSingletons.lookup(LayeredHostedImageHeapMapCollector.class); + } + + public void registerReachableHostedImageHeapMap(LayeredImageHeapMap layeredImageHeapMap) { + currentLayerReachableMapsKeys.add(layeredImageHeapMap.getMapKey()); + } + + public boolean isMapKeyReachableInPreviousLayer(String mapKey) { + return previousLayerReachableMapKeys.contains(mapKey); + } + + public void registerPreviousLayerHostedImageHeapMap(HostedImageHeapMap hostedImageHeapMap) { + previousLayerReachableMaps.add(hostedImageHeapMap); + } + + public List> getPreviousLayerReachableMaps() { + return previousLayerReachableMaps; + } + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY; + } + + @Override + public PersistFlags preparePersist(ImageSingletonWriter writer) { + Set reachableMapKeys = new HashSet<>(currentLayerReachableMapsKeys); + if (previousLayerReachableMapKeys != null) { + reachableMapKeys.addAll(previousLayerReachableMapKeys); + } + writer.writeStringList("reachableMapKeys", reachableMapKeys.stream().toList()); + return PersistFlags.CREATE; + } + + @SuppressWarnings("unused") + public static Object createFromLoader(ImageSingletonLoader loader) { + List previousLayerReachableMapKeys = loader.readStringList("reachableMapKeys"); + return new LayeredHostedImageHeapMapCollector(previousLayerReachableMapKeys); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/LayeredImageHeapMap.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/LayeredImageHeapMap.java new file mode 100644 index 000000000000..6c52806af764 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/LayeredImageHeapMap.java @@ -0,0 +1,239 @@ +/* + * 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.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.EconomicSet; +import org.graalvm.collections.Equivalence; +import org.graalvm.collections.MapCursor; +import org.graalvm.collections.UnmodifiableMapCursor; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +/** + * This map lives across multiple layers through the {@link LayeredImageHeapMapStore}. It has a + * different behavior at build time and run time. At build time, the map from the current layer is + * accessed and interacted with. At run time, the maps from all layers are iterated over and handled + * together. + */ +public class LayeredImageHeapMap implements EconomicMap { + private final Equivalence strategy; + private final String mapKey; + /** + * This boolean stores the order in which the maps are iterated over and determines how values + * are overwritten across layers. If true, the first map to be checked is the base layer one and + * if false, it is the application layer map. The first map to be checked is the one whose value + * will have priority over the others. + */ + private final boolean fromBaseToApp; + + public LayeredImageHeapMap(Equivalence strategy, String mapKey) { + this(strategy, mapKey, false); + } + + public LayeredImageHeapMap(Equivalence strategy, String mapKey, boolean fromBaseToApp) { + this.strategy = strategy; + this.mapKey = mapKey; + this.fromBaseToApp = fromBaseToApp; + } + + @Override + public V put(K key, V value) { + /* The returned value needs to be the first value to be found. */ + List singletons = Arrays.stream(LayeredImageHeapMapStore.layeredSingletons()).toList(); + boolean first = true; + for (var singleton : fromBaseToApp ? singletons : singletons.reversed()) { + V oldValue; + if (first) { + oldValue = getRuntimeMap(singleton).put(key, value); + first = false; + } else { + oldValue = getRuntimeMap(singleton).get(key); + } + if (oldValue != null) { + return oldValue; + } + } + return null; + } + + @Override + public void clear() { + for (var singleton : LayeredImageHeapMapStore.layeredSingletons()) { + getRuntimeMap(singleton).clear(); + } + } + + @Override + public V removeKey(K key) { + /* + * The returned value needs to be the first value to be found. However, all the other values + * still need to be removed as we would still find an older value otherwise. + */ + List singletons = Arrays.stream(LayeredImageHeapMapStore.layeredSingletons()).toList(); + V previousValue = null; + for (var singleton : fromBaseToApp ? singletons : singletons.reversed()) { + V oldValue = getRuntimeMap(singleton).removeKey(key); + if (oldValue != null && previousValue == null) { + previousValue = oldValue; + } + } + return previousValue; + } + + @Override + public V get(K key) { + List singletons = Arrays.stream(LayeredImageHeapMapStore.layeredSingletons()).toList(); + for (var singleton : fromBaseToApp ? singletons : singletons.reversed()) { + EconomicMap singletonMap = getRuntimeMap(singleton); + if (singletonMap.containsKey(key)) { + return singletonMap.get(key); + } + } + return null; + } + + @Override + public boolean containsKey(K key) { + for (var singleton : LayeredImageHeapMapStore.layeredSingletons()) { + if (getRuntimeMap(singleton).containsKey(key)) { + return true; + } + } + + return false; + } + + @Override + public int size() { + int size = 0; + for (var singleton : LayeredImageHeapMapStore.layeredSingletons()) { + size += getRuntimeMap(singleton).size(); + } + return size; + } + + @Override + public boolean isEmpty() { + for (var singleton : LayeredImageHeapMapStore.layeredSingletons()) { + if (!getRuntimeMap(singleton).isEmpty()) { + return false; + } + } + return true; + } + + @Override + public Iterable getValues() { + return getIterator(UnmodifiableMapCursor::getValue); + } + + @Override + public Iterable getKeys() { + return getIterator(UnmodifiableMapCursor::getKey); + } + + private Iterable getIterator(Function, E> getElement) { + var cursor = getEntries(); + List elements = new ArrayList<>(); + while (cursor.advance()) { + elements.add(getElement.apply(cursor)); + } + return elements; + } + + @Override + public MapCursor getEntries() { + /* + * In case of duplicate keys, the first key found need to have priority over the other keys. + * The keys need to be stored to ensure each of them is processed once. + */ + List singletons = Arrays.stream(LayeredImageHeapMapStore.layeredSingletons()).toList(); + singletons = fromBaseToApp ? singletons : singletons.reversed(); + Iterator> cursors = singletons.stream().map(singleton -> getRuntimeMap(singleton).getEntries()).iterator(); + return new MapCursor<>() { + private MapCursor current = cursors.next(); + private final EconomicSet keys = EconomicSet.create(strategy); + + @Override + public void remove() { + current.remove(); + } + + @Override + public boolean advance() { + boolean advance = current.advance(); + if (!advance) { + if (cursors.hasNext()) { + current = cursors.next(); + if (keys.add(current.getKey())) { + return true; + } + } + } + return advance; + } + + @Override + public K getKey() { + return current.getKey(); + } + + @Override + public V getValue() { + return current.getValue(); + } + + @Override + public V setValue(V newValue) { + return current.setValue(newValue); + } + }; + } + + @Override + public void replaceAll(BiFunction function) { + for (var singleton : LayeredImageHeapMapStore.layeredSingletons()) { + getRuntimeMap(singleton).replaceAll(function); + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + public String getMapKey() { + return mapKey; + } + + @SuppressWarnings("unchecked") + private EconomicMap getRuntimeMap(LayeredImageHeapMapStore singleton) { + return (EconomicMap) singleton.getImageHeapMapStore().get(mapKey); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/LayeredImageHeapMapStore.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/LayeredImageHeapMapStore.java new file mode 100644 index 000000000000..84061fc0cf56 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/LayeredImageHeapMapStore.java @@ -0,0 +1,60 @@ +/* + * 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.util; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonSupport; +import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton; + +public class LayeredImageHeapMapStore implements MultiLayeredImageSingleton, UnsavedSingleton { + private final Map> imageHeapMapStore = new HashMap<>(); + + @Platforms(Platform.HOSTED_ONLY.class) + public static LayeredImageHeapMapStore currentLayer() { + return LayeredImageSingletonSupport.singleton().lookup(LayeredImageHeapMapStore.class, false, true); + } + + public static LayeredImageHeapMapStore[] layeredSingletons() { + return MultiLayeredImageSingleton.getAllLayers(LayeredImageHeapMapStore.class); + } + + public Map> getImageHeapMapStore() { + return imageHeapMapStore; + } + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.ALL_ACCESS; + } +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalCompilerSupport.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalCompilerSupport.java index 6c62a8862c8e..60cf84a44496 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalCompilerSupport.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalCompilerSupport.java @@ -53,8 +53,8 @@ */ public class GraalCompilerSupport { - public final EconomicMap, NodeClass> nodeClasses = ImageHeapMap.create(); - public final EconomicMap, LIRInstructionClass> instructionClasses = ImageHeapMap.create(); + public final EconomicMap, NodeClass> nodeClasses = ImageHeapMap.create("nodeClasses"); + public final EconomicMap, LIRInstructionClass> instructionClasses = ImageHeapMap.create("instructionClasses"); public HashMap, EconomicMap, List>> matchRuleRegistry; protected EconomicMap, BasePhase.BasePhaseStatistics> basePhaseStatistics; @@ -79,8 +79,8 @@ public void setMatchRuleRegistry(HashMap, Econom @Platforms(Platform.HOSTED_ONLY.class) public static void allocatePhaseStatisticsCache() { - GraalCompilerSupport.get().basePhaseStatistics = ImageHeapMap.create(); - GraalCompilerSupport.get().lirPhaseStatistics = ImageHeapMap.create(); + GraalCompilerSupport.get().basePhaseStatistics = ImageHeapMap.create("basePhaseStatistics"); + GraalCompilerSupport.get().lirPhaseStatistics = ImageHeapMap.create("lirPhaseStatistics"); } /* Invoked once for every class that is reachable in the native image. */ 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 b54a33153edd..d40ecfa0face 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 @@ -168,6 +168,8 @@ import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.snippets.SnippetRuntime; import com.oracle.svm.core.util.InterruptImageBuilding; +import com.oracle.svm.core.util.LayeredHostedImageHeapMapCollector; +import com.oracle.svm.core.util.LayeredImageHeapMapStore; import com.oracle.svm.core.util.ObservableImageHeapMapProvider; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; @@ -943,6 +945,17 @@ protected void setupNativeImage(String imageName, OptionValues options, Map layeredImageHeapMap) { + LayeredHostedImageHeapMapCollector.singleton().registerReachableHostedImageHeapMap(layeredImageHeapMap); + } } - return hostedImageHeapMap.runtimeMap; + return hostedImageHeapMap.getRuntimeMap(); } else if (obj instanceof HostedImageHeapList hostedImageHeapList) { if (BuildPhaseProvider.isAnalysisFinished()) { VMError.guarantee(allLists.contains(hostedImageHeapList), "ImageHeapList reachable after analysis that was not seen during analysis"); @@ -73,10 +79,13 @@ private Object replaceHostedWithRuntime(Object obj) { @Override public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + if (ImageLayerBuildingSupport.buildingExtensionLayer()) { + allMaps.addAll(LayeredHostedImageHeapMapCollector.singleton().getPreviousLayerReachableMaps()); + } for (var hostedImageHeapMap : allMaps) { if (needsUpdate(hostedImageHeapMap)) { update(hostedImageHeapMap); - access.rescanObject(hostedImageHeapMap.runtimeMap); + access.rescanObject(hostedImageHeapMap.getCurrentLayerMap()); access.requireAnalysisIteration(); } } @@ -108,7 +117,7 @@ public void afterImageWrite(AfterImageWriteAccess access) { for (var hostedImageHeapMap : allMaps) { if (needsUpdate(hostedImageHeapMap)) { throw VMError.shouldNotReachHere("ImageHeapMap modified after static analysis:%n%s%n%s", - hostedImageHeapMap, hostedImageHeapMap.runtimeMap); + hostedImageHeapMap, hostedImageHeapMap.getCurrentLayerMap()); } } for (var hostedImageHeapList : allLists) { @@ -121,7 +130,7 @@ public void afterImageWrite(AfterImageWriteAccess access) { } private static boolean needsUpdate(HostedImageHeapMap hostedMap) { - EconomicMap runtimeMap = hostedMap.runtimeMap; + EconomicMap runtimeMap = hostedMap.getCurrentLayerMap(); if (hostedMap.size() != runtimeMap.size()) { return true; } @@ -137,7 +146,7 @@ private static boolean needsUpdate(HostedImageHeapMap hostedMap) { } private static void update(HostedImageHeapMap hostedMap) { - hostedMap.runtimeMap.clear(); - hostedMap.runtimeMap.putAll(hostedMap); + hostedMap.getCurrentLayerMap().clear(); + hostedMap.getCurrentLayerMap().putAll(hostedMap); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java index 04e96e716b3f..53c3d8708ac0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java @@ -24,8 +24,6 @@ */ package com.oracle.svm.hosted.reflect.proxy; -import java.lang.reflect.Field; - import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeProxyCreationSupport; @@ -40,7 +38,6 @@ import com.oracle.svm.core.reflect.proxy.DynamicProxySupport; import com.oracle.svm.hosted.FallbackFeature; import com.oracle.svm.hosted.FeatureImpl; -import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; @@ -50,7 +47,6 @@ @AutomaticallyRegisteredFeature public final class DynamicProxyFeature implements InternalFeature { private int loadedConfigurations; - private Field proxyCacheField; private ProxyRegistry proxyRegistry; @Override @@ -78,8 +74,6 @@ public void duringSetup(DuringSetupAccess a) { loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "dynamic proxy", ConfigurationFiles.Options.DynamicProxyConfigurationFiles, ConfigurationFiles.Options.DynamicProxyConfigurationResources, ConfigurationFile.DYNAMIC_PROXY.getFileName()); - - proxyCacheField = access.findField(DynamicProxySupport.class, "proxyCache"); } private static ProxyRegistry proxyRegistry() { @@ -91,12 +85,6 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { proxyRegistry().setAnalysisAccess(access); } - @Override - public void duringAnalysis(DuringAnalysisAccess a) { - DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; - access.rescanField(ImageSingletons.lookup(DynamicProxyRegistry.class), proxyCacheField); - } - @Override public void beforeCompilation(BeforeCompilationAccess access) { if (!ImageSingletons.contains(FallbackFeature.class)) { diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/NodeClassSupport.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/NodeClassSupport.java index 17ad657366a7..c8403ed5d882 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/NodeClassSupport.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/NodeClassSupport.java @@ -31,7 +31,7 @@ import com.oracle.truffle.api.nodes.NodeClass; class NodeClassSupport { - final EconomicMap, NodeClass> nodeClasses = ImageHeapMap.create(); + final EconomicMap, NodeClass> nodeClasses = ImageHeapMap.create("nodeClasses"); static NodeClassSupport singleton() { return ImageSingletons.lookup(NodeClassSupport.class);