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 4645e620f7e4..1154ca22ba89 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 @@ -244,6 +244,7 @@ private void addKnownClass(String name, ConditionalRuntimeValue cond) { private void addKnownClass(String name, Consumer>> callback, ConditionalRuntimeValue cond) { Boolean previousLayerData = previousLayerClasses.get(name); + /* GR-66387: The runtime condition should be combined across layers. */ if (previousLayerData == null || (!previousLayerData && cond.getValueUnconditionally() != NEGATIVE_QUERY)) { callback.accept(knownClasses); } 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 847d9eb5e63e..61b5af345699 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 @@ -341,6 +341,7 @@ private void updateTimeStamp() { private void addResource(ModuleResourceKey key, ConditionalRuntimeValue entry) { Boolean previousLayerData = ImageLayerBuildingSupport.buildingImageLayer() ? previousLayerResources.get(key.toString()) : null; + /* GR-66387: The runtime condition should be combined across layers. */ if (previousLayerData == null || (!previousLayerData && entry.getValueUnconditionally() != NEGATIVE_QUERY_MARKER)) { resources.put(key, entry); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java index 0a2725d62903..c12cc0e38bec 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java @@ -434,7 +434,7 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { * enabled. Until a clear SVM core separation is created and included in the base layer, * those types should be manually registered as instantiated before the analysis. */ - if (HostedImageLayerBuildingSupport.buildingSharedLayer()) { + if (HostedImageLayerBuildingSupport.buildingImageLayer()) { String reason = "Included in the base image"; access.getMetaAccess().lookupJavaType(ReflectionUtil.lookupClass(false, "com.oracle.svm.core.jdk.resources.CompressedGlobTrie.LiteralNode")).registerAsInstantiated(reason); access.getMetaAccess().lookupJavaType(ReflectionUtil.lookupClass(false, "com.oracle.svm.core.jdk.resources.CompressedGlobTrie.DoubleStarNode")).registerAsInstantiated(reason); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 5a4428055e52..92fc71c56e34 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -57,6 +57,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -66,6 +68,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import org.graalvm.nativeimage.ImageSingletons; @@ -85,8 +88,15 @@ import com.oracle.svm.core.MissingRegistrationUtils; import com.oracle.svm.core.configure.ConditionalRuntimeValue; import com.oracle.svm.core.configure.RuntimeConditionSet; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.imagelayer.BuildingImageLayerPredicate; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; import com.oracle.svm.core.reflect.SubstrateAccessor; import com.oracle.svm.core.reflect.target.ReflectionSubstitutionSupport; import com.oracle.svm.core.util.VMError; @@ -112,6 +122,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl private final SubstrateAnnotationExtractor annotationExtractor; private BeforeAnalysisAccessImpl analysisAccess; private final ClassForNameSupport classForNameSupport; + private LayeredReflectionDataBuilder layeredReflectionDataBuilder; private boolean sealed; @@ -174,6 +185,9 @@ public void duringSetup(AnalysisMetaAccess analysisMetaAccess, AnalysisUniverse registerConditionalConfiguration(conditionalTask.condition, (cnd) -> universe.getBigbang().postTask(debug -> conditionalTask.task.accept(cnd))); } pendingConditionalTasks.clear(); + if (ImageLayerBuildingSupport.buildingImageLayer()) { + layeredReflectionDataBuilder = LayeredReflectionDataBuilder.singleton(); + } } public void beforeAnalysis(BeforeAnalysisAccessImpl beforeAnalysisAccess) { @@ -444,6 +458,12 @@ private void registerMethod(ConfigurationCondition cnd, boolean queriedOnly, Exe AnalysisMethod analysisMethod = metaAccess.lookupJavaMethod(reflectExecutable); AnalysisType declaringType = analysisMethod.getDeclaringClass(); + + if (layeredReflectionDataBuilder != null && layeredReflectionDataBuilder.isMethodRegistered(analysisMethod)) { + /* GR-66387: The runtime condition should be combined across layers. */ + return; + } + var classMethods = registeredMethods.computeIfAbsent(declaringType, t -> new ConcurrentHashMap<>()); var shouldRegisterReachabilityHandler = classMethods.isEmpty(); @@ -606,6 +626,11 @@ private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Fiel AnalysisField analysisField = metaAccess.lookupJavaField(reflectField); AnalysisType declaringClass = analysisField.getDeclaringClass(); + if (layeredReflectionDataBuilder != null && layeredReflectionDataBuilder.isFieldRegistered(analysisField)) { + /* GR-66387: The runtime condition should be combined across layers. */ + return; + } + var classFields = registeredFields.computeIfAbsent(declaringClass, t -> new ConcurrentHashMap<>()); boolean exists = classFields.containsKey(analysisField); boolean shouldRegisterReachabilityHandler = classFields.isEmpty(); @@ -1391,4 +1416,98 @@ public static void registerField(ReflectionDataBuilder reflectionDataBuilder, bo reflectionDataBuilder.runConditionalInAnalysisTask(ConfigurationCondition.alwaysTrue(), (cnd) -> reflectionDataBuilder.registerField(cnd, queriedOnly, field)); } } + + @AutomaticallyRegisteredImageSingleton(onlyWith = BuildingImageLayerPredicate.class) + public static class LayeredReflectionDataBuilder implements LayeredImageSingleton { + public static final String METHODS = "methods"; + public static final String FIELDS = "fields"; + public static final String REFLECTION_DATA_BUILDER = "reflection data builder"; + public static final String REFLECTION_DATA_BUILDER_CLASSES = REFLECTION_DATA_BUILDER + " classes"; + /** + * The methods registered for reflection in the previous layers. The key of the map is the + * id of the declaring type and the set contains the method ids. + */ + private final Map> previousLayerRegisteredMethods; + /** + * The fields registered for reflection in the previous layers. The key of the map is the id + * of the declaring type and the set contains the field ids. + */ + private final Map> previousLayerRegisteredFields; + + public LayeredReflectionDataBuilder() { + this(Map.of(), Map.of()); + } + + private LayeredReflectionDataBuilder(Map> previousLayerRegisteredMethods, Map> previousLayerRegisteredFields) { + this.previousLayerRegisteredMethods = previousLayerRegisteredMethods; + this.previousLayerRegisteredFields = previousLayerRegisteredFields; + } + + public static LayeredReflectionDataBuilder singleton() { + return ImageSingletons.lookup(LayeredReflectionDataBuilder.class); + } + + public boolean isMethodRegistered(AnalysisMethod analysisMethod) { + return isElementRegistered(previousLayerRegisteredMethods, analysisMethod.getDeclaringClass(), analysisMethod.getId()); + } + + public boolean isFieldRegistered(AnalysisField analysisField) { + return isElementRegistered(previousLayerRegisteredFields, analysisField.getDeclaringClass(), analysisField.getId()); + } + + private static boolean isElementRegistered(Map> previousLayerRegisteredElements, AnalysisType declaringClass, int elementId) { + Set previousLayerRegisteredElementIds = previousLayerRegisteredElements.get(declaringClass.getId()); + if (declaringClass.isInBaseLayer() && previousLayerRegisteredElementIds != null) { + return previousLayerRegisteredElementIds.contains(elementId); + } + return false; + } + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY; + } + + @Override + public PersistFlags preparePersist(ImageSingletonWriter writer) { + ReflectionDataBuilder reflectionDataBuilder = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); + persistRegisteredElements(writer, reflectionDataBuilder.registeredMethods, AnalysisMethod::getId, METHODS); + persistRegisteredElements(writer, reflectionDataBuilder.registeredFields, AnalysisField::getId, FIELDS); + return PersistFlags.CREATE; + } + + private static void persistRegisteredElements(ImageSingletonWriter writer, Map> registeredElements, Function getId, String element) { + List classes = new ArrayList<>(); + for (var entry : registeredElements.entrySet()) { + classes.add(entry.getKey().getId()); + writer.writeIntList(getElementKeyName(element, entry.getKey().getId()), entry.getValue().keySet().stream().map(getId).toList()); + } + writer.writeIntList(getClassesKeyName(element), classes); + } + + @SuppressWarnings("unused") + public static Object createFromLoader(ImageSingletonLoader loader) { + var previousLayerRegisteredMethods = loadRegisteredElements(loader, METHODS); + var previousLayerRegisteredFields = loadRegisteredElements(loader, FIELDS); + return new LayeredReflectionDataBuilder(previousLayerRegisteredMethods, previousLayerRegisteredFields); + } + + private static Map> loadRegisteredElements(ImageSingletonLoader loader, String element) { + Map> previousLayerRegisteredElements = new HashMap<>(); + var classes = loader.readIntList(getClassesKeyName(element)); + for (int key : classes) { + var elements = loader.readIntList(getElementKeyName(element, key)).stream().collect(Collectors.toUnmodifiableSet()); + previousLayerRegisteredElements.put(key, elements); + } + return Collections.unmodifiableMap(previousLayerRegisteredElements); + } + + private static String getClassesKeyName(String element) { + return REFLECTION_DATA_BUILDER_CLASSES + " " + element; + } + + private static String getElementKeyName(String element, int typeId) { + return REFLECTION_DATA_BUILDER + " " + element + " " + typeId; + } + } }