From 4a2ebf2afa7628e23f1aba766c9a55ca7f9b28f2 Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Tue, 9 Jul 2024 09:21:31 +0200 Subject: [PATCH] Change ClassLoaderFeature to be layer-aware. --- .../svm/core/jdk/ClassLoaderSupport.java | 112 ------- .../jdk/Target_java_lang_ClassLoader.java | 34 --- .../oracle/svm/hosted/BootLoaderSupport.java | 21 +- .../oracle/svm/hosted/ClassLoaderFeature.java | 106 ++++++- .../hosted/NativeImageSystemClassLoader.java | 21 +- .../svm/hosted/SystemInOutErrFeature.java | 18 +- .../analysis/DynamicHubInitializer.java | 5 +- .../svm/hosted/image/NativeImageHeap.java | 6 +- .../CrossLayerConstantRegistry.java | 6 + .../CrossLayerConstantRegistryFeature.java | 43 ++- .../LayeredImageHeapObjectAdder.java | 74 +++++ .../imagelayer/LoadImageSingletonFeature.java | 7 +- .../svm/hosted/jdk/ForkJoinPoolFeature.java | 4 +- .../HostedClassLoaderPackageManagement.java | 282 ++++++++++++++++++ 14 files changed, 547 insertions(+), 192 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ClassLoaderSupport.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredImageHeapObjectAdder.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/HostedClassLoaderPackageManagement.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ClassLoaderSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ClassLoaderSupport.java deleted file mode 100644 index 15d175aa1c7e..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ClassLoaderSupport.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2020, 2020, 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.jdk; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import jdk.graal.compiler.debug.GraalError; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; - -import com.oracle.svm.core.BuildPhaseProvider; -import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.util.ReflectionUtil; - -/** - * This class stores information about which packages need to be stored within each ClassLoader. - * This information is used by {@link Target_java_lang_ClassLoader} to reset the package - * information. The package information must be reset because: - *
    - *
  1. Many more classes may be loaded during native-image generation time than will be reachable - * during a normal execution.
  2. - *
  3. Each ClassLoader should only initially store packages for classes which are initialized at - * build-time. Classes (re)initialized during runtime should have their respective package linked - * then.
  4. - *
  5. During native-image generation time, a custom system class loader is used - * (NativeImageSystemClassLoader) to load application classes. However, before heap creation, - * classes which were loaded by NativeImageSystemClassLoader are updated to point to the default - * system class loader via ClassLoaderFeature. Hence, the default system class loader must be - * updated to contain references to all appropriate packages.
  6. - *
- */ -@AutomaticallyRegisteredImageSingleton -@Platforms(Platform.HOSTED_ONLY.class) -public final class ClassLoaderSupport { - - private static ClassLoaderSupport singleton() { - return ImageSingletons.lookup(ClassLoaderSupport.class); - } - - ClassLoaderSupport() { - } - - private final ConcurrentMap> registeredPackages = new ConcurrentHashMap<>(); - - private static final Method packageGetPackageInfo = ReflectionUtil.lookupMethod(Package.class, "getPackageInfo"); - - /** - * Register the package with its classLoader. If the registration was missing return the entire - * package registry of the classLoader, return null otherwise. - */ - public static ConcurrentMap registerPackage(ClassLoader classLoader, String packageName, Package packageValue) { - assert classLoader != null; - assert packageName != null; - assert packageValue != null; - VMError.guarantee(!BuildPhaseProvider.isAnalysisFinished(), "Packages should be collected and registered during analysis."); - - /* - * Eagerly initialize the field Package.packageInfo, which stores the .package-info class - * (if present) with the annotations for the package. We want that class to be available - * without having to register it manually in the reflection configuration file. - * - * Note that we either need to eagerly initialize that field (the approach chosen) or - * force-reset it to null for all packages, otherwise there can be transient problems when - * the lazy initialization happens in the image builder after the static analysis. - */ - try { - packageGetPackageInfo.invoke(packageValue); - } catch (IllegalAccessException | InvocationTargetException e) { - throw GraalError.shouldNotReachHere(e); // ExcludeFromJacocoGeneratedReport - } - - var loaderPackages = singleton().registeredPackages.computeIfAbsent(classLoader, k -> new ConcurrentHashMap<>()); - Package previous = loaderPackages.putIfAbsent(packageName, packageValue); - if (previous == null) { - /* Return the class loader packages if the new package was missing. */ - return loaderPackages; - } - return null; - } - - public static ConcurrentHashMap getRegisteredPackages(ClassLoader classLoader) { - VMError.guarantee(BuildPhaseProvider.isAnalysisFinished(), "Packages are stable only after analysis."); - return singleton().registeredPackages.get(classLoader); - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java index 91913c122ff5..a8f9d949113d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java @@ -29,7 +29,6 @@ import java.security.ProtectionDomain; import java.util.Collections; import java.util.Enumeration; -import java.util.HashMap; import java.util.Set; import java.util.Vector; import java.util.WeakHashMap; @@ -43,7 +42,6 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.hub.PredefinedClassesSupport; import com.oracle.svm.core.util.VMError; @@ -72,13 +70,6 @@ public final class Target_java_lang_ClassLoader { @Alias @RecomputeFieldValue(kind = Kind.NewInstanceWhenNotNull, declClass = ConcurrentHashMap.class)// private ConcurrentHashMap parallelLockMap; - /** - * Recompute ClassLoader.packages; See {@link ClassLoaderSupport} for explanation on why this - * information must be reset. - */ - @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = PackageFieldTransformer.class)// - private ConcurrentHashMap packages; - @Alias // private static ClassLoader scl; @@ -330,31 +321,6 @@ private static Class defineClass0(ClassLoader loader, Class lookup, String final class Target_java_lang_AssertionStatusDirectives { } -class PackageFieldTransformer implements FieldValueTransformerWithAvailability { - - @Override - public ValueAvailability valueAvailability() { - return ValueAvailability.AfterAnalysis; - } - - @Override - public Object transform(Object receiver, Object originalValue) { - assert receiver instanceof ClassLoader; - - /* JDK9+ stores packages in a ConcurrentHashMap, while 8 and before use a HashMap. */ - boolean useConcurrentHashMap = originalValue instanceof ConcurrentHashMap; - - /* Retrieving initial package state for this class loader. */ - ConcurrentHashMap packages = ClassLoaderSupport.getRegisteredPackages((ClassLoader) receiver); - if (packages == null) { - /* No package state available - have to create clean state. */ - return useConcurrentHashMap ? new ConcurrentHashMap() : new HashMap(); - } else { - return useConcurrentHashMap ? packages : new HashMap<>(packages); - } - } -} - @TargetClass(className = "java.lang.ClassLoader", innerClass = "ParallelLoaders") final class Target_java_lang_ClassLoader_ParallelLoaders { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/BootLoaderSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/BootLoaderSupport.java index 160351a4ef25..ba6889d4b8f5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/BootLoaderSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/BootLoaderSupport.java @@ -26,23 +26,20 @@ import java.lang.reflect.Method; -import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; +import jdk.internal.loader.ClassLoaders; + public class BootLoaderSupport { - /** - * Gets the boot loader or {@code null} if it does not exist. - */ + private static ClassLoader bootLoader; + public static ClassLoader getBootLoader() { - Class classLoadersClass; - try { - classLoadersClass = Class.forName("jdk.internal.loader.ClassLoaders"); - Method method = ReflectionUtil.lookupMethod(classLoadersClass, "bootLoader"); - Object r = method.invoke(null); - return (ClassLoader) r; - } catch (ReflectiveOperationException e) { - throw VMError.shouldNotReachHere("Cannot get access to the boot loader", e); + if (bootLoader != null) { + return bootLoader; } + Method method = ReflectionUtil.lookupMethod(ClassLoaders.class, "bootLoader"); + bootLoader = ReflectionUtil.invokeMethod(method, null); + return bootLoader; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderFeature.java index 2bf320c4e495..8f14286bed92 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderFeature.java @@ -24,22 +24,51 @@ */ package com.oracle.svm.hosted; -import com.oracle.svm.core.feature.InternalFeature; +import java.util.concurrent.ConcurrentHashMap; + +import com.oracle.graal.pointsto.heap.ImageHeapConstant; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.imagelayer.CrossLayerConstantRegistry; +import com.oracle.svm.hosted.jdk.HostedClassLoaderPackageManagement; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.internal.loader.ClassLoaders; @AutomaticallyRegisteredFeature public class ClassLoaderFeature implements InternalFeature { + private static final String APP_KEY_NAME = "ClassLoader#App"; + private static final String PLATFORM_KEY_NAME = "ClassLoader#Platform"; + private static final String BOOT_KEY_NAME = "ClassLoader#Boot"; + private static final NativeImageSystemClassLoader nativeImageSystemClassLoader = NativeImageSystemClassLoader.singleton(); + private static final ClassLoader bootClassLoader; + private static final ClassLoader platformClassLoader; + + static { + if (ImageLayerBuildingSupport.buildingImageLayer()) { + platformClassLoader = ClassLoaders.platformClassLoader(); + bootClassLoader = BootLoaderSupport.getBootLoader(); + } else { + platformClassLoader = null; + bootClassLoader = null; + } + } + public static ClassLoader getRuntimeClassLoader(ClassLoader original) { - if (needsReplacement(original)) { + if (replaceWithAppClassLoader(original)) { return nativeImageSystemClassLoader.defaultSystemClassLoader; } + return original; } - private static boolean needsReplacement(ClassLoader loader) { + private static boolean replaceWithAppClassLoader(ClassLoader loader) { if (loader == nativeImageSystemClassLoader) { return true; } @@ -50,14 +79,79 @@ private static boolean needsReplacement(ClassLoader loader) { } private Object runtimeClassLoaderObjectReplacer(Object replaceCandidate) { - if (replaceCandidate instanceof ClassLoader) { - return getRuntimeClassLoader((ClassLoader) replaceCandidate); + if (replaceCandidate instanceof ClassLoader loader) { + return getRuntimeClassLoader(loader); } return replaceCandidate; } + ImageHeapConstant replaceClassLoadersWithLayerConstant(CrossLayerConstantRegistry registry, Object object) { + if (object instanceof ClassLoader loader) { + if (replaceWithAppClassLoader(loader) || loader == nativeImageSystemClassLoader.defaultSystemClassLoader) { + return registry.getConstant(APP_KEY_NAME); + } else if (loader == platformClassLoader) { + return registry.getConstant(PLATFORM_KEY_NAME); + } else if (loader == bootClassLoader) { + return registry.getConstant(BOOT_KEY_NAME); + } else { + throw VMError.shouldNotReachHere("Currently unhandled class loader seen in extension layer: %s", loader); + } + } + + return null; + } + @Override public void duringSetup(DuringSetupAccess access) { - access.registerObjectReplacer(this::runtimeClassLoaderObjectReplacer); + var packageManager = HostedClassLoaderPackageManagement.singleton(); + var registry = CrossLayerConstantRegistry.singletonOrNull(); + if (ImageLayerBuildingSupport.buildingImageLayer()) { + packageManager.initialize(nativeImageSystemClassLoader.defaultSystemClassLoader, registry); + } + + if (ImageLayerBuildingSupport.firstImageBuild()) { + access.registerObjectReplacer(this::runtimeClassLoaderObjectReplacer); + } else { + var config = (FeatureImpl.DuringSetupAccessImpl) access; + config.registerObjectToConstantReplacer(obj -> replaceClassLoadersWithLayerConstant(registry, obj)); + // relink packages defined in the prior layers + config.registerObjectToConstantReplacer(packageManager::replaceWithPriorLayerPackage); + } + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + var packagesField = ReflectionUtil.lookupField(ClassLoader.class, "packages"); + access.registerFieldValueTransformer(packagesField, new FieldValueTransformerWithAvailability() { + + @Override + public ValueAvailability valueAvailability() { + return ValueAvailability.AfterAnalysis; + } + + @Override + public Object transform(Object receiver, Object originalValue) { + assert receiver instanceof ClassLoader : receiver; + assert originalValue instanceof ConcurrentHashMap : "Underlying representation has changed: " + originalValue; + + /* Retrieving initial package state for this class loader. */ + ConcurrentHashMap packages = HostedClassLoaderPackageManagement.singleton().getRegisteredPackages((ClassLoader) receiver); + /* If no package state is available then we must create a clean state. */ + return packages == null ? new ConcurrentHashMap<>() : packages; + } + }); + + if (ImageLayerBuildingSupport.buildingInitialLayer()) { + /* + * Note we cannot register these heap constants until the field value transformer has + * been registered. Otherwise there is a race between this feature and + * ObservableImageHeapMapProviderImpl#beforeAnalysis, as during heap scanning all + * fieldValueInterceptors will be computed for the scanned objects. + */ + var registry = CrossLayerConstantRegistry.singletonOrNull(); + registry.registerHeapConstant(APP_KEY_NAME, nativeImageSystemClassLoader.defaultSystemClassLoader); + registry.registerHeapConstant(PLATFORM_KEY_NAME, platformClassLoader); + registry.registerHeapConstant(BOOT_KEY_NAME, bootClassLoader); + } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageSystemClassLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageSystemClassLoader.java index 9f8aff5492ba..0177fb6a2b9b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageSystemClassLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageSystemClassLoader.java @@ -65,6 +65,10 @@ public NativeImageSystemClassLoader(ClassLoader defaultSystemClassLoader) { systemIOWrappers.replaceSystemOutErr(); } + /* + * Note this is not an image singleton; this ClassLoader is installed while starting + * NativeImageGeneratorRunner. + */ public static NativeImageSystemClassLoader singleton() { ClassLoader loader = ClassLoader.getSystemClassLoader(); if (loader instanceof NativeImageSystemClassLoader) { @@ -87,14 +91,17 @@ public void setNativeImageClassLoader(ClassLoader nativeImageClassLoader) { this.nativeImageClassLoader = nativeImageClassLoader; } - private boolean isNativeImageClassLoader(ClassLoader current, ClassLoader c) { - ClassLoader loader = current; + /** + * Checks class loaders match. {@code start} is searched up to the system class loader. + */ + private boolean matchesClassLoaderOrParent(ClassLoader start, ClassLoader candidate) { + ClassLoader current = start; do { - if (loader == c) { + if (current == candidate) { return true; } - loader = loader.getParent(); - } while (loader != defaultSystemClassLoader); + current = current.getParent(); + } while (current != defaultSystemClassLoader); return false; } @@ -103,12 +110,12 @@ public boolean isNativeImageClassLoader(ClassLoader c) { if (loader == null) { return false; } - return isNativeImageClassLoader(nativeImageClassLoader, c); + return matchesClassLoaderOrParent(nativeImageClassLoader, c); } public boolean isDisallowedClassLoader(ClassLoader c) { for (ClassLoader disallowedClassLoader : disallowedClassLoaders) { - if (isNativeImageClassLoader(disallowedClassLoader, c)) { + if (matchesClassLoaderOrParent(disallowedClassLoader, c)) { return true; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SystemInOutErrFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SystemInOutErrFeature.java index 95dc963840fa..91c45ea13563 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SystemInOutErrFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SystemInOutErrFeature.java @@ -38,7 +38,6 @@ import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.jdk.SystemInOutErrSupport; import com.oracle.svm.core.layeredimagesingleton.FeatureSingleton; -import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.imagelayer.CrossLayerConstantRegistry; /** @@ -80,16 +79,13 @@ public void duringSetup(DuringSetupAccess access) { if (ImageLayerBuildingSupport.firstImageBuild()) { if (ImageLayerBuildingSupport.buildingInitialLayer()) { var registry = CrossLayerConstantRegistry.singletonOrNull(); - registry.registerConstantCandidate(SYSTEM_IN_KEY_NAME, runtime.in()); - registry.registerConstantCandidate(SYSTEM_OUT_KEY_NAME, runtime.out()); - registry.registerConstantCandidate(SYSTEM_ERR_KEY_NAME, runtime.err()); + registry.registerHeapConstant(SYSTEM_IN_KEY_NAME, runtime.in()); + registry.registerHeapConstant(SYSTEM_OUT_KEY_NAME, runtime.out()); + registry.registerHeapConstant(SYSTEM_ERR_KEY_NAME, runtime.err()); } access.registerObjectReplacer(this::replaceStreamsWithRuntimeObject); } else { var registry = CrossLayerConstantRegistry.singletonOrNull(); - VMError.guarantee(registry.constantExists(SYSTEM_IN_KEY_NAME), "Missing Constant %s", SYSTEM_IN_KEY_NAME); - VMError.guarantee(registry.constantExists(SYSTEM_OUT_KEY_NAME), "Missing Constant %s", SYSTEM_OUT_KEY_NAME); - VMError.guarantee(registry.constantExists(SYSTEM_ERR_KEY_NAME), "Missing Constant %s", SYSTEM_ERR_KEY_NAME); ((FeatureImpl.DuringSetupAccessImpl) access).registerObjectToConstantReplacer(obj -> replaceStreamsWithLayerConstant(registry, obj)); } } @@ -111,13 +107,13 @@ Object replaceStreamsWithRuntimeObject(Object object) { } } - ImageHeapConstant replaceStreamsWithLayerConstant(CrossLayerConstantRegistry idTracking, Object object) { + ImageHeapConstant replaceStreamsWithLayerConstant(CrossLayerConstantRegistry registry, Object object) { if (object == hostedIn) { - return idTracking.getConstant(SYSTEM_IN_KEY_NAME); + return registry.getConstant(SYSTEM_IN_KEY_NAME); } else if (object == hostedOut) { - return idTracking.getConstant(SYSTEM_OUT_KEY_NAME); + return registry.getConstant(SYSTEM_OUT_KEY_NAME); } else if (object == hostedErr) { - return idTracking.getConstant(SYSTEM_ERR_KEY_NAME); + return registry.getConstant(SYSTEM_ERR_KEY_NAME); } else { return null; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/DynamicHubInitializer.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/DynamicHubInitializer.java index 3abd4c117231..4109dd5787c6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/DynamicHubInitializer.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/DynamicHubInitializer.java @@ -43,7 +43,6 @@ import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.classinitialization.ClassInitializationInfo; import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.jdk.ClassLoaderSupport; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.BootLoaderSupport; @@ -52,6 +51,7 @@ import com.oracle.svm.hosted.SVMHost; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.hosted.classinitialization.SimulateClassInitializerSupport; +import com.oracle.svm.hosted.jdk.HostedClassLoaderPackageManagement; import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.ConstantReflectionProvider; @@ -164,8 +164,7 @@ private static void registerPackage(ImageHeapScanner heapScanner, Class javaC ClassLoader runtimeClassLoader = ClassLoaderFeature.getRuntimeClassLoader(classloader); VMError.guarantee(runtimeClassLoader != null, "Class loader missing for class %s", hub.getName()); String packageName = hub.getPackageName(); - var loaderPackages = ClassLoaderSupport.registerPackage(runtimeClassLoader, packageName, packageValue); - heapScanner.rescanObject(loaderPackages); + HostedClassLoaderPackageManagement.singleton().registerPackage(runtimeClassLoader, packageName, packageValue, heapScanner::rescanObject); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java index e4f166501df2..92343ee5f5bb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java @@ -76,7 +76,7 @@ import com.oracle.svm.hosted.config.DynamicHubLayout; import com.oracle.svm.hosted.config.HybridLayout; import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; -import com.oracle.svm.hosted.imagelayer.LoadImageSingletonFeature; +import com.oracle.svm.hosted.imagelayer.LayeredImageHeapObjectAdder; import com.oracle.svm.hosted.meta.HostedArrayClass; import com.oracle.svm.hosted.meta.HostedClass; import com.oracle.svm.hosted.meta.HostedConstantReflectionProvider; @@ -218,8 +218,8 @@ public void addInitialObjects() { addObjectsPhase.allow(); internStringsPhase.allow(); - if (ImageSingletons.contains(LoadImageSingletonFeature.class)) { - ImageSingletons.lookup(LoadImageSingletonFeature.class).addInitialObjects(this, hUniverse); + if (ImageSingletons.contains(LayeredImageHeapObjectAdder.class)) { + ImageSingletons.lookup(LayeredImageHeapObjectAdder.class).addInitialObjects(this, hUniverse); } addStaticFields(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistry.java index 2ca5d971a337..0473856f641c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistry.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistry.java @@ -58,4 +58,10 @@ static CrossLayerConstantRegistry singletonOrNull() { * {@link #constantExists}. */ void registerConstantCandidate(String keyName, Object obj); + + /** + * Registers a value which must be stored in this layer's heap. Later layers can access it via + * {@link #getConstant}. + */ + void registerHeapConstant(String keyName, Object obj); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java index 1d98b5137a4f..07f2255f1d94 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/CrossLayerConstantRegistryFeature.java @@ -29,6 +29,7 @@ import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.graalvm.nativeimage.ImageSingletons; @@ -42,8 +43,11 @@ 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.ObservableImageHeapMapProvider; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.image.NativeImageHeap; +import com.oracle.svm.hosted.meta.HostedUniverse; @AutomaticallyRegisteredFeature public class CrossLayerConstantRegistryFeature implements InternalFeature, FeatureSingleton, CrossLayerConstantRegistry { @@ -52,7 +56,8 @@ public class CrossLayerConstantRegistryFeature implements InternalFeature, Featu boolean sealed = false; ImageLayerLoader loader; - Map constantCandidates; + ConcurrentMap constantCandidates; + ConcurrentMap requiredConstants; @Override public boolean isInConfiguration(IsInConfigurationAccess access) { @@ -70,6 +75,7 @@ public void afterRegistration(AfterRegistrationAccess access) { if (ImageLayerBuildingSupport.buildingSharedLayer()) { constantCandidates = new ConcurrentHashMap<>(); + requiredConstants = ObservableImageHeapMapProvider.create(); } ImageSingletons.add(CrossLayerConstantRegistry.class, this); } @@ -77,6 +83,18 @@ public void afterRegistration(AfterRegistrationAccess access) { @Override public void duringSetup(DuringSetupAccess access) { loader = ((FeatureImpl.DuringSetupAccessImpl) access).getUniverse().getImageLayerLoader(); + if (ImageLayerBuildingSupport.buildingSharedLayer()) { + LayeredImageHeapObjectAdder.singleton().registerObjectAdder(this::addInitialObjects); + } + } + + private void addInitialObjects(NativeImageHeap heap, HostedUniverse hUniverse) { + String addReason = "Registered as a required heap constant within the CrossLayerConstantRegistry"; + + for (Object constant : requiredConstants.values()) { + ImageHeapConstant singletonConstant = (ImageHeapConstant) hUniverse.getSnippetReflection().forObject(constant); + heap.addConstant(singletonConstant, false, addReason); + } } @Override @@ -103,9 +121,24 @@ public void beforeImageWrite(BeforeImageWriteAccess access) { } } } + + assert verifyConstantsInstalled(config); } } + private boolean verifyConstantsInstalled(FeatureImpl.BeforeImageWriteAccessImpl config) { + var snippetReflection = config.getHostedUniverse().getSnippetReflection(); + var heap = config.getImage().getHeap(); + + for (var requiredConstant : requiredConstants.values()) { + var constant = (ImageHeapConstant) snippetReflection.forObject(requiredConstant); + var objectInfo = heap.getConstantInfo(constant); + assert objectInfo != null && objectInfo.getOffset() >= 0 : "Constant is required to be in heap " + requiredConstant; + } + + return true; + } + private void checkSealed() { VMError.guarantee(!sealed, "Id tracking is sealed"); } @@ -118,6 +151,14 @@ public void registerConstantCandidate(String keyName, Object obj) { VMError.guarantee(previous == null && !constantExists(keyName), "This key has been registered before: %s", keyName); } + @Override + public void registerHeapConstant(String keyName, Object obj) { + VMError.guarantee(obj != null, "CrossLayer constants are expected to be non-null."); + registerConstantCandidate(keyName, obj); + var previous = requiredConstants.putIfAbsent(keyName, obj); + VMError.guarantee(previous == null, "This key has been registered before: %s", keyName); + } + @Override public ImageHeapConstant getConstant(String keyName) { checkSealed(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredImageHeapObjectAdder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredImageHeapObjectAdder.java new file mode 100644 index 000000000000..6cb927742956 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayeredImageHeapObjectAdder.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024, 2024, 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.util.EnumSet; +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiConsumer; + +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.imagelayer.BuildingImageLayerPredicate; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; +import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.image.NativeImageHeap; +import com.oracle.svm.hosted.meta.HostedUniverse; + +/** + * When building layered images, sometimes we know an object will be needed by a subsequent layer. + * In these cases it is necessary to manually add the object to the image heap. + *

+ * Object adders are executed late, after the analysis has completed and after the shadow heap has + * been sealed. The code using this feature must ensure that the object has been seen by the static + * analysis, and it has been added to the shadow heap, e.g., by triggering a shadow heap re-scan. + */ +@AutomaticallyRegisteredImageSingleton(onlyWith = BuildingImageLayerPredicate.class) +public class LayeredImageHeapObjectAdder implements UnsavedSingleton { + + private final Set> objectAdders = new HashSet<>(); + private boolean sealed = false; + + public static LayeredImageHeapObjectAdder singleton() { + return ImageSingletons.lookup(LayeredImageHeapObjectAdder.class); + } + + public void registerObjectAdder(BiConsumer adder) { + VMError.guarantee(!sealed, "Object adder is registered too late"); + objectAdders.add(adder); + } + + public void addInitialObjects(NativeImageHeap heap, HostedUniverse hUniverse) { + sealed = true; + objectAdders.forEach(adder -> adder.accept(heap, hUniverse)); + } + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY; + } +} 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 acb791e20fc0..2e08e2deb691 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 @@ -143,6 +143,11 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec }); } + @Override + public void duringSetup(DuringSetupAccess access) { + LayeredImageHeapObjectAdder.singleton().registerObjectAdder(this::addInitialObjects); + } + @Override public void beforeAnalysis(BeforeAnalysisAccess access) { var config = (FeatureImpl.BeforeAnalysisAccessImpl) access; @@ -261,7 +266,7 @@ public Map getConstantToTableSlotMap() { * Ensure all objects needed for {@link MultiLayeredImageSingleton}s and * {@link ApplicationLayerOnlyImageSingleton}s are installed in the heap. */ - public void addInitialObjects(NativeImageHeap heap, HostedUniverse hUniverse) { + private void addInitialObjects(NativeImageHeap heap, HostedUniverse hUniverse) { String addReason = "Read via the layered image singleton support"; /* diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ForkJoinPoolFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ForkJoinPoolFeature.java index 3ee3bcaa1f79..dc30c7344f99 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ForkJoinPoolFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ForkJoinPoolFeature.java @@ -62,9 +62,9 @@ private static Object replaceCommonPoolWithRuntimeObject(Object original, Deferr return original; } - private static ImageHeapConstant replaceCommonPoolWithLayerConstant(CrossLayerConstantRegistry idTracking, Object original) { + private static ImageHeapConstant replaceCommonPoolWithLayerConstant(CrossLayerConstantRegistry registry, Object original) { if (original == ForkJoinPool.commonPool()) { - return idTracking.getConstant(KEY_NAME); + return registry.getConstant(KEY_NAME); } return null; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/HostedClassLoaderPackageManagement.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/HostedClassLoaderPackageManagement.java new file mode 100644 index 000000000000..e3f9fd4170d4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/HostedClassLoaderPackageManagement.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2024, 2024, 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.jdk; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.graal.pointsto.heap.ImageHeapConstant; +import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.jdk.Target_java_lang_ClassLoader; +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.VMError; +import com.oracle.svm.hosted.BootLoaderSupport; +import com.oracle.svm.hosted.ClassLoaderFeature; +import com.oracle.svm.hosted.imagelayer.CrossLayerConstantRegistry; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.graal.compiler.debug.Assertions; +import jdk.graal.compiler.debug.GraalError; +import jdk.internal.loader.ClassLoaders; + +/** + * This class stores information about which packages need to be stored within each ClassLoader. + * This information is used by {@link Target_java_lang_ClassLoader} to reset the package + * information. The package information must be reset because: + *
    + *
  1. Many more classes may be loaded during native-image generation time than will be reachable + * during a normal execution.
  2. + *
  3. Each ClassLoader should only initially store packages for classes which are initialized at + * build-time. Classes (re)initialized during runtime should have their respective package linked + * then.
  4. + *
  5. During native-image generation time, a custom system class loader is used + * (NativeImageSystemClassLoader) to load application classes. However, before heap creation, + * classes which were loaded by NativeImageSystemClassLoader are updated to point to the default + * system class loader via ClassLoaderFeature. Hence, the default system class loader must be + * updated to contain references to all appropriate packages.
  6. + *
+ * + * For layered images, we record the packages stored in earlier layers so that references in + * subsequent layers can be appropriately relinked. Currently in extension layers we expect only the + * system class loader to have new packages registered. + */ +@AutomaticallyRegisteredImageSingleton +public class HostedClassLoaderPackageManagement implements LayeredImageSingleton { + + private static final String APP_KEY = "AppPackageNames"; + private static final String PLATFORM_KEY = "PlatformPackageNames"; + private static final String BOOT_KEY = "BootPackageNames"; + + private static final Method packageGetPackageInfo = ReflectionUtil.lookupMethod(Package.class, "getPackageInfo"); + + private static final ClassLoader platformClassLoader; + private static final ClassLoader bootClassLoader; + private static final Method moduleLookup; + + private final boolean inExtensionLayer; + private final boolean inSharedLayer; + + /** + * Contains package names seen in the prior layers. + */ + private final Set priorAppPackageNames; + private final Set priorPlatformPackageNames; + private final Set priorBootPackageNames; + + private final ConcurrentMap> registeredPackages = new ConcurrentHashMap<>(); + + private ClassLoader appClassLoader; + private CrossLayerConstantRegistry registry; + + static { + if (ImageLayerBuildingSupport.buildingImageLayer()) { + platformClassLoader = ClassLoaders.platformClassLoader(); + bootClassLoader = BootLoaderSupport.getBootLoader(); + moduleLookup = ImageLayerBuildingSupport.buildingExtensionLayer() ? ReflectionUtil.lookupMethod(ReflectionUtil.lookupClass(false, "java.lang.NamedPackage"), "module") : null; + } else { + platformClassLoader = null; + bootClassLoader = null; + moduleLookup = null; + } + } + + public HostedClassLoaderPackageManagement() { + inExtensionLayer = false; + inSharedLayer = ImageLayerBuildingSupport.buildingSharedLayer(); + priorAppPackageNames = null; + priorPlatformPackageNames = null; + priorBootPackageNames = null; + } + + private HostedClassLoaderPackageManagement(Set appPackages, Set platformPackages, Set bootPackages) { + VMError.guarantee(ImageLayerBuildingSupport.buildingExtensionLayer(), "Unexpected invocation"); + inExtensionLayer = true; + inSharedLayer = ImageLayerBuildingSupport.buildingSharedLayer(); + priorAppPackageNames = appPackages; + priorPlatformPackageNames = platformPackages; + priorBootPackageNames = bootPackages; + } + + public static HostedClassLoaderPackageManagement singleton() { + return ImageSingletons.lookup(HostedClassLoaderPackageManagement.class); + } + + private boolean isPackageRegistered(ClassLoader classLoader, String packageName, Package packageValue) { + if (inExtensionLayer) { + if (classLoader == bootClassLoader) { + var result = priorBootPackageNames.contains(packageName); + VMError.guarantee(result, "Newly seen boot package %s", packageValue); + return true; + } else if (classLoader == platformClassLoader) { + var result = priorPlatformPackageNames.contains(packageName); + VMError.guarantee(result, "Newly seen platform package %s", packageValue); + return true; + } else if (classLoader == appClassLoader) { + if (priorAppPackageNames.contains(packageName)) { + return true; + } + } else { + throw VMError.shouldNotReachHere("Currently unhandled class loader seen in extension layer: %s", classLoader); + } + } + + var loaderPackages = registeredPackages.get(classLoader); + return loaderPackages != null && loaderPackages.containsKey(packageName); + } + + /** + * Register the package with its runtimeClassLoader. + */ + public void registerPackage(ClassLoader runtimeClassLoader, String packageName, Package packageValue, Consumer objectScanner) { + assert runtimeClassLoader != null; + assert packageName != null; + assert packageValue != null; + VMError.guarantee(!BuildPhaseProvider.isAnalysisFinished(), "Packages should be collected and registered during analysis."); + + if (isPackageRegistered(runtimeClassLoader, packageName, packageValue)) { + // already registered; nothing to do. + return; + } + + /* + * Eagerly initialize the field Package.packageInfo, which stores the .package-info class + * (if present) with the annotations for the package. We want that class to be available + * without having to register it manually in the reflection configuration file. + * + * Note that we either need to eagerly initialize that field (the approach chosen) or + * force-reset it to null for all packages, otherwise there can be transient problems when + * the lazy initialization happens in the image builder after the static analysis. + * + * Note in extension layers we know the class loader is only allowed to be the app class + * loader. + */ + try { + packageGetPackageInfo.invoke(packageValue); + } catch (IllegalAccessException | InvocationTargetException e) { + throw GraalError.shouldNotReachHere(e); // ExcludeFromJacocoGeneratedReport + } + + var loaderPackages = registeredPackages.computeIfAbsent(runtimeClassLoader, k -> new ConcurrentHashMap<>()); + Object previous = loaderPackages.putIfAbsent(packageName, packageValue); + if (previous == null) { + /* Scan the class loader packages if the new package was missing. */ + objectScanner.accept(loaderPackages); + if (inSharedLayer && runtimeClassLoader == appClassLoader) { + /* + * We must register this package so that it can be relinked in subsequent layers. + */ + registry.registerHeapConstant(generateKeyName(packageValue.getName()), packageValue); + } + } + } + + public ImageHeapConstant replaceWithPriorLayerPackage(Object obj) { + if (obj instanceof Package hostedPackage) { + /* + * Determine the package's ClassLoader + */ + Module module = ReflectionUtil.invokeMethod(moduleLookup, hostedPackage); + ClassLoader classLoader = ClassLoaderFeature.getRuntimeClassLoader(module.getClassLoader()); + VMError.guarantee(classLoader == appClassLoader, "Currently unhandled class loader seen in extension layer: %s", classLoader); + + var keyName = generateKeyName(hostedPackage.getName()); + if (registry.constantExists(keyName)) { + return registry.getConstant(keyName); + } + } + + return null; + } + + private static String generateKeyName(String packageName) { + return String.format("AppClassLoaderPackage#%s", packageName); + } + + public ConcurrentHashMap getRegisteredPackages(ClassLoader classLoader) { + VMError.guarantee(BuildPhaseProvider.isAnalysisFinished(), "Packages are stable only after analysis."); + VMError.guarantee(ImageLayerBuildingSupport.firstImageBuild(), "All classloaders should be transformed in the first layer %s", classLoader); + + return registeredPackages.get(classLoader); + } + + public void initialize(ClassLoader newAppClassLoader, CrossLayerConstantRegistry newRegistry) { + assert appClassLoader == null && newAppClassLoader != null : Assertions.errorMessage(appClassLoader, newAppClassLoader); + assert registry == null && newRegistry != null : Assertions.errorMessage(registry, newRegistry); + + appClassLoader = newAppClassLoader; + registry = newRegistry; + } + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY; + } + + private List collectPackageNames(ClassLoader classLoader, Set priorPackageNames) { + var map = registeredPackages.get(classLoader); + if (priorPackageNames != null && map != null) { + return Stream.concat(map.keySet().stream(), priorPackageNames.stream()).toList(); + } else if (priorPackageNames == null && map != null) { + return map.keySet().stream().toList(); + } else if (priorPackageNames != null && map == null) { + return priorPackageNames.stream().toList(); + } else { + throw VMError.shouldNotReachHere("Bad state: %s %s", priorPackageNames, map); + } + } + + @Override + public LayeredImageSingleton.PersistFlags preparePersist(ImageSingletonWriter writer) { + writer.writeStringList(APP_KEY, collectPackageNames(appClassLoader, priorAppPackageNames)); + writer.writeStringList(PLATFORM_KEY, collectPackageNames(platformClassLoader, priorPlatformPackageNames)); + writer.writeStringList(BOOT_KEY, collectPackageNames(bootClassLoader, priorBootPackageNames)); + + return PersistFlags.CREATE; + } + + @SuppressWarnings("unused") + public static Object createFromLoader(ImageSingletonLoader loader) { + var appSet = loader.readStringList(APP_KEY).stream().collect(Collectors.toUnmodifiableSet()); + var platformSet = loader.readStringList(PLATFORM_KEY).stream().collect(Collectors.toUnmodifiableSet()); + var bootSet = loader.readStringList(BOOT_KEY).stream().collect(Collectors.toUnmodifiableSet()); + + return new HostedClassLoaderPackageManagement(appSet, platformSet, bootSet); + } +}