From 1fcdae6133de26a3b9525f2ad950f825e3c896ab Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 9 Dec 2024 13:23:35 +0100 Subject: [PATCH] WIP, make spring petclinic work --- .../nativeimage/hosted/RuntimeReflection.java | 8 +-- .../nativeimage/impl/ReflectionRegistry.java | 1 + .../impl/RuntimeReflectionSupport.java | 4 +- .../graal/pointsto/ClassInclusionPolicy.java | 3 +- .../graal/pointsto/PointsToAnalysis.java | 2 +- .../config/ParserConfigurationAdapter.java | 2 +- .../com/oracle/svm/core/SubstrateOptions.java | 6 +- .../LegacyReflectionConfigurationParser.java | 2 +- ...ReflectionConfigurationParserDelegate.java | 2 +- .../configure/ReflectionMetadataParser.java | 2 +- .../svm/hosted/ClassLoaderSupportImpl.java | 6 +- .../hosted/NativeImageClassLoaderSupport.java | 56 +++++++++---------- .../svm/hosted/NativeImageGenerator.java | 35 +++++++----- .../src/com/oracle/svm/hosted/SVMHost.java | 3 +- .../config/ReflectionRegistryAdapter.java | 4 +- .../svm/hosted/config/RegistryAdapter.java | 4 +- .../hosted/reflect/ReflectionDataBuilder.java | 25 ++++++--- .../serialize/SerializationFeature.java | 32 +++++++---- .../SubstitutionReflectivityFilter.java | 3 + .../com/oracle/svm/util/ReflectionUtil.java | 16 +++++- vm/mx.vm/mx_vm_benchmark.py | 33 +++++------ 21 files changed, 146 insertions(+), 103 deletions(-) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java index 07ef05fedfff..809194c58892 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java @@ -201,7 +201,7 @@ public static void registerAllDeclaredMethods(Class declaringClass) { * @since 23.0 */ public static void registerAllConstructors(Class declaringClass) { - ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllConstructorsQuery(ConfigurationCondition.alwaysTrue(), true, declaringClass); + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllConstructorsQuery(ConfigurationCondition.alwaysTrue(), false, declaringClass); } /** @@ -211,7 +211,7 @@ public static void registerAllConstructors(Class declaringClass) { * @since 23.0 */ public static void registerAllDeclaredConstructors(Class declaringClass) { - ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredConstructorsQuery(ConfigurationCondition.alwaysTrue(), true, declaringClass); + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredConstructorsQuery(ConfigurationCondition.alwaysTrue(), false, declaringClass); } /** @@ -221,7 +221,7 @@ public static void registerAllDeclaredConstructors(Class declaringClass) { * @since 23.0 */ public static void registerAllFields(Class declaringClass) { - ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllFieldsQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllFields(ConfigurationCondition.alwaysTrue(), declaringClass); } /** @@ -231,7 +231,7 @@ public static void registerAllFields(Class declaringClass) { * @since 23.0 */ public static void registerAllDeclaredFields(Class declaringClass) { - ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredFieldsQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredFields(ConfigurationCondition.alwaysTrue(), declaringClass); } /** diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java index 9d0534347cfb..686d692c261f 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java @@ -45,6 +45,7 @@ import java.util.Arrays; public interface ReflectionRegistry { + // TODO thread this true behind MRE flag default void register(ConfigurationCondition condition, Class... classes) { Arrays.stream(classes).forEach(clazz -> register(condition, false, clazz)); } diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java index 68e78b2c975b..6415b4753a78 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java @@ -46,9 +46,9 @@ public interface RuntimeReflectionSupport extends ReflectionRegistry { void registerAllDeclaredMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz); - void registerAllFieldsQuery(ConfigurationCondition condition, Class clazz); + void registerAllFields(ConfigurationCondition condition, Class clazz); - void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class clazz); + void registerAllDeclaredFields(ConfigurationCondition condition, Class clazz); void registerAllConstructorsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java index 6cf09305e1af..9f3541214b3a 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java @@ -34,6 +34,7 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.api.replacements.Fold; @@ -55,7 +56,7 @@ public void setBigBang(BigBang bb) { } public static boolean isClassIncludedBase(Class cls) { - Class enclosingClass = cls.getEnclosingClass(); + Class enclosingClass = ReflectionUtil.linkageSafeQuery(cls, null, Class::getEnclosingClass); return !Feature.class.isAssignableFrom(cls) && !AnnotationAccess.isAnnotationPresent(cls, TargetClass.class) && (enclosingClass == null || isClassIncludedBase(enclosingClass)); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java index b66e5c8e728a..eec29baeae40 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java @@ -651,7 +651,7 @@ public void onTypeInstantiated(AnalysisType type) { /* Register the type as instantiated with all its super types. */ assert type.isInstantiated() : type; - AnalysisError.guarantee(type.isArray() || (type.isInstanceClass() && !type.isAbstract())); + AnalysisError.guarantee(type.isArray() || (type.isInstanceClass() && !type.isAbstract()), "Type %s must be either an array, or a non abstract instance class", type.getName()); TypeState typeState = TypeState.forExactType(this, type, true); TypeState typeStateNonNull = TypeState.forExactType(this, type, false); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java index 39657123cec2..c09dcc01768a 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java @@ -55,7 +55,7 @@ public TypeResult resolveType(UnresolvedConfigurationConditio } @Override - public void registerType(UnresolvedConfigurationCondition condition, ConfigurationType type) { + public void registerType(UnresolvedConfigurationCondition condition, ConfigurationType type, boolean registerMetadata) { VMError.guarantee(condition.equals(type.getCondition()), "condition is already a part of the type"); configuration.add(type); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index abc387a08ae2..046bf8ec7ed8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -1284,19 +1284,19 @@ public enum ReportingMode { public static final HostedOptionKey IncludeAllFromModule = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); @Option(help = "Include all classes, methods, fields, and resources from a given module for dynamic access", type = OptionType.Debug) // - public static final HostedOptionKey IncludeAllMetadataForModule = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); + public static final HostedOptionKey EnableDynamicAccessForModule = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); @Option(help = "Include all classes, methods, fields, and resources from given paths", type = OptionType.Debug) // public static final HostedOptionKey IncludeAllFromPath = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); @Option(help = "Include all classes, methods, fields, and resources from given paths for dynamic access", type = OptionType.Debug) // - public static final HostedOptionKey IncludeAllMetadataForClassPathEntry = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); + public static final HostedOptionKey EnableDynamicAccessForClassPathEntry = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); @Option(help = "Include all classes, methods, fields, and resources from the class path", type = OptionType.Debug) // public static final HostedOptionKey IncludeAllFromClassPath = new HostedOptionKey<>(false); @Option(help = "Include all classes, methods, fields, and resources for dynamic access for the whole classpath", type = OptionType.Debug) // - public static final HostedOptionKey IncludeAllMetadataForClassPath = new HostedOptionKey<>(false); + public static final HostedOptionKey EnableDynamicAccess = new HostedOptionKey<>(false); public static boolean includeAll() { return IncludeAllFromModule.hasBeenSet() || IncludeAllFromPath.hasBeenSet() || IncludeAllFromClassPath.hasBeenSet(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java index a123418dc670..7d071e1dbdcc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java @@ -91,7 +91,7 @@ protected void parseClass(EconomicMap data) { C queryCondition = isType ? conditionResolver.alwaysTrue() : condition; T clazz = result.get(); - delegate.registerType(conditionResult.get(), clazz); + delegate.registerType(conditionResult.get(), clazz, false); registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz)); registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz)); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java index 748262afabb7..df461a7f02a2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java @@ -32,7 +32,7 @@ public interface ReflectionConfigurationParserDelegate { TypeResult resolveType(C condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives); - void registerType(C condition, T type); + void registerType(C condition, T type, boolean registerMetadata); void registerPublicClasses(C condition, T type); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java index 33bd0028be6c..1fe8453a07f0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java @@ -84,7 +84,7 @@ protected void parseClass(EconomicMap data) { C queryCondition = conditionResolver.alwaysTrue(); T clazz = result.get(); - delegate.registerType(conditionResult.get(), clazz); + delegate.registerType(conditionResult.get(), clazz, true); delegate.registerDeclaredClasses(queryCondition, clazz); delegate.registerRecordComponents(queryCondition, clazz); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java index 045c7e0c7622..0dcf1e2bd304 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java @@ -116,8 +116,8 @@ public void collectResources(ResourceCollector resourceCollector) { /* Collect remaining resources from classpath */ classLoaderSupport.classpath().stream().parallel().forEach(classpathFile -> { - boolean includeCurrent = classLoaderSupport.getJavaPathsToInclude().contains(classpathFile) || classLoaderSupport.includeAllFromClassPath() || - classLoaderSupport.getPathsToIncludeMetadata().contains(classpathFile) || classLoaderSupport.isIncludeAllMetadataFromClassPath(); + boolean includeCurrent = classLoaderSupport.getPathsToInclude().contains(classpathFile) || classLoaderSupport.includeAllFromClassPath() || + classLoaderSupport.getClassPathEntriesToEnableDynamicAccess().contains(classpathFile) || classLoaderSupport.isEnableDynamicAccessForClassPath(); try { if (Files.isDirectory(classpathFile)) { scanDirectory(classpathFile, resourceCollector, includeCurrent); @@ -134,7 +134,7 @@ private void collectResourceFromModule(ResourceCollector resourceCollector, Reso ModuleReference moduleReference = info.resolvedModule.reference(); try (ModuleReader moduleReader = moduleReference.open()) { boolean includeCurrent = classLoaderSupport.getModuleNamesToInclude().contains(info.resolvedModule().name()) || - classLoaderSupport.getModuleNamesToIncludeMetadata().contains(info.resolvedModule().name()); + classLoaderSupport.getModuleNamesToEnableDynamicAccess().contains(info.resolvedModule().name()); List resourcesFound = new ArrayList<>(); moduleReader.list().forEach(resourceName -> { var conditionsWithOrigins = shouldIncludeEntry(info.module, resourceCollector, resourceName, moduleReference.location().orElse(null), includeCurrent); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java index d90b3c73a15e..d193ff80a812 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java @@ -27,9 +27,9 @@ import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromClassPath; import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromModule; import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromPath; -import static com.oracle.svm.core.SubstrateOptions.IncludeAllMetadataForModule; -import static com.oracle.svm.core.SubstrateOptions.IncludeAllMetadataForClassPath; -import static com.oracle.svm.core.SubstrateOptions.IncludeAllMetadataForClassPathEntry; +import static com.oracle.svm.core.SubstrateOptions.EnableDynamicAccessForModule; +import static com.oracle.svm.core.SubstrateOptions.EnableDynamicAccess; +import static com.oracle.svm.core.SubstrateOptions.EnableDynamicAccessForClassPathEntry; import static com.oracle.svm.core.util.VMError.guarantee; import static com.oracle.svm.core.util.VMError.shouldNotReachHere; @@ -131,14 +131,14 @@ public final class NativeImageClassLoaderSupport { public final AnnotationExtractor annotationExtractor; - private Set javaModuleNamesToInclude; - private Set moduleNamesToIncludeMetadata; + private Set moduleNamesToInclude; + private Set moduleNamesToEnableDynamicAccess; - private Set javaPathsToInclude; - private Set pathsToIncludeMetadata; + private Set pathsToInclude; + private Set classPathEntriesToEnableDynamicAccess; private boolean includeAllFromClassPath; - private boolean includeAllMetadataFromClassPath; + private boolean enableDynamicAccessForClassPath; private Optional libGraalLoader; private List classLoaders; @@ -254,16 +254,16 @@ private static Path stringToPath(String path) { } public void loadAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoader) { - guarantee(javaModuleNamesToInclude == null, "This method should be executed only once."); + guarantee(moduleNamesToInclude == null, "This method should be executed only once."); - javaModuleNamesToInclude = collectAndVerifyModulesFromOption(IncludeAllFromModule); - moduleNamesToIncludeMetadata = collectAndVerifyModulesFromOption(IncludeAllMetadataForModule); + moduleNamesToInclude = collectAndVerifyModulesFromOption(IncludeAllFromModule); + moduleNamesToEnableDynamicAccess = collectAndVerifyModulesFromOption(EnableDynamicAccessForModule); - javaPathsToInclude = collectAndVerifyPathsFromOption(IncludeAllFromPath); - pathsToIncludeMetadata = collectAndVerifyPathsFromOption(IncludeAllMetadataForClassPathEntry); + pathsToInclude = collectAndVerifyPathsFromOption(IncludeAllFromPath); + classPathEntriesToEnableDynamicAccess = collectAndVerifyPathsFromOption(EnableDynamicAccessForClassPathEntry); includeAllFromClassPath = IncludeAllFromClassPath.getValue(parsedHostedOptions); - includeAllMetadataFromClassPath = IncludeAllMetadataForClassPath.getValue(parsedHostedOptions); + enableDynamicAccessForClassPath = EnableDynamicAccess.getValue(parsedHostedOptions); new LoadClassHandler(executor, imageClassLoader).run(); @@ -763,7 +763,7 @@ private void run() { "org.graalvm.nativebridge")); Set additionalSystemModules = upgradeAndSystemModuleFinder.findAll().stream().map(v -> v.descriptor().name()).collect(Collectors.toSet()); - additionalSystemModules.retainAll(getModuleNamesToIncludeMetadata()); + additionalSystemModules.retainAll(getModuleNamesToEnableDynamicAccess()); requiresInit.addAll(additionalSystemModules); for (ModuleReference moduleReference : upgradeAndSystemModuleFinder.findAll()) { @@ -790,8 +790,8 @@ private void initModule(ModuleReference moduleReference) { } try (ModuleReader moduleReader = moduleReference.open()) { Module module = optionalModule.get(); - final boolean includeUnconditionally = javaModuleNamesToInclude.contains(module.getName()); - final boolean includeForReflection = moduleNamesToIncludeMetadata.contains(module.getName()); + final boolean includeUnconditionally = moduleNamesToInclude.contains(module.getName()); + final boolean includeForReflection = moduleNamesToEnableDynamicAccess.contains(module.getName()); var container = moduleReference.location().orElseThrow(); if (ModuleLayer.boot().equals(module.getLayer())) { builderURILocations.add(container); @@ -811,8 +811,8 @@ private void initModule(ModuleReference moduleReference) { } private void loadClassesFromPath(Path path) { - final boolean includeUnconditionally = javaPathsToInclude.contains(path) || includeAllFromClassPath; - final boolean includeAllMetadata = pathsToIncludeMetadata.contains(path) || includeAllMetadataFromClassPath; + final boolean includeUnconditionally = pathsToInclude.contains(path) || includeAllFromClassPath; + final boolean includeAllMetadata = classPathEntriesToEnableDynamicAccess.contains(path) || enableDynamicAccessForClassPath; if (ClasspathUtils.isJar(path)) { try { URI container = path.toAbsolutePath().toUri(); @@ -1031,27 +1031,27 @@ public void reportBuilderClassesInApplication() { } public Set getModuleNamesToInclude() { - return javaModuleNamesToInclude; + return moduleNamesToInclude; } - public Set getModuleNamesToIncludeMetadata() { - return moduleNamesToIncludeMetadata; + public Set getModuleNamesToEnableDynamicAccess() { + return moduleNamesToEnableDynamicAccess; } - public Set getJavaPathsToInclude() { - return javaPathsToInclude; + public Set getPathsToInclude() { + return pathsToInclude; } - public Set getPathsToIncludeMetadata() { - return pathsToIncludeMetadata; + public Set getClassPathEntriesToEnableDynamicAccess() { + return classPathEntriesToEnableDynamicAccess; } public boolean includeAllFromClassPath() { return includeAllFromClassPath; } - public boolean isIncludeAllMetadataFromClassPath() { - return includeAllMetadataFromClassPath; + public boolean isEnableDynamicAccessForClassPath() { + return enableDynamicAccessForClassPath; } public List> getClassesToIncludeUnconditionally() { 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 9b495acb095b..b3f0f6f5baf7 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 @@ -32,6 +32,8 @@ import java.io.IOException; import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.file.FileSystems; @@ -75,7 +77,9 @@ import org.graalvm.nativeimage.hosted.RuntimeSerialization; import org.graalvm.nativeimage.impl.AnnotationExtractor; import org.graalvm.nativeimage.impl.CConstantValueSupport; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; +import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import org.graalvm.nativeimage.impl.SizeOfSupport; import org.graalvm.word.PointerBase; @@ -1085,23 +1089,28 @@ protected void setupNativeImage(String imageName, OptionValues options, Map cls) { - RuntimeReflection.register(cls); - RuntimeJNIAccess.register(cls); - for (Method declaredMethod : cls.getDeclaredMethods()) { - RuntimeReflection.register(declaredMethod); + private static void registerClassFullyForReflection(Class clazz) { + RuntimeReflection.register(clazz); + + RuntimeReflection.registerAllDeclaredFields(clazz); + RuntimeReflection.registerAllDeclaredMethods(clazz); + RuntimeReflection.registerAllDeclaredConstructors(clazz); + + RuntimeJNIAccess.register(clazz); + for (Method declaredMethod : ReflectionUtil.linkageSafeQuery(clazz, new Method[0], Class::getDeclaredMethods)) { RuntimeJNIAccess.register(declaredMethod); } - RuntimeReflection.registerAllDeclaredFields(cls); - RuntimeReflection.registerAllFields(cls); - RuntimeReflection.registerAllMethods(cls); - RuntimeReflection.registerAllDeclaredMethods(cls); - - for (var declaredField : cls.getDeclaredFields()) { - RuntimeReflection.register(declaredField); + for (Constructor declaredConstructor : ReflectionUtil.linkageSafeQuery(clazz, new Constructor[0], Class::getDeclaredConstructors)) { + RuntimeJNIAccess.register(declaredConstructor); + } + for (var declaredField : ReflectionUtil.linkageSafeQuery(clazz, new Field[0], Class::getDeclaredFields)) { RuntimeJNIAccess.register(declaredField); } - RuntimeSerialization.register(cls); + + RuntimeSerialization.register(clazz); + if (!(clazz.isArray() || clazz.isInterface() || clazz.isPrimitive() || Modifier.isAbstract(clazz.getModifiers()))) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.alwaysTrue(), true, clazz); + } } protected void registerEntryPointStubs(Map entryPoints) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index 6d9c11ff30c9..82519c8470bb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -33,6 +33,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; +import java.lang.reflect.RecordComponent; import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; @@ -649,7 +650,7 @@ public void checkType(ResolvedJavaType type, AnalysisUniverse universe) { throw new UnsupportedFeatureException(message); } if (originalClass.isRecord()) { - for (var recordComponent : originalClass.getRecordComponents()) { + for (var recordComponent : ReflectionUtil.linkageSafeQuery(originalClass, new RecordComponent[0], Class::getRecordComponents)) { if (WordBase.class.isAssignableFrom(recordComponent.getType())) { throw UserError.abort("Records cannot use Word types. " + "The equals/hashCode/toString implementation of records uses method handles, and Word types are not supported as parameters of method handle invocations. " + diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java index c2b13640d2f6..c31aa46bb8f9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java @@ -48,8 +48,8 @@ public class ReflectionRegistryAdapter extends RegistryAdapter { } @Override - public void registerType(ConfigurationCondition condition, Class type) { - super.registerType(condition, type); + public void registerType(ConfigurationCondition condition, Class type, boolean registerMetadata) { + super.registerType(condition, type, registerMetadata); if (Proxy.isProxyClass(type)) { proxyRegistry.accept(condition, Arrays.stream(type.getInterfaces()).map(Class::getTypeName).toList()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java index 76ea23a764d6..a89cb2e1afa1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java @@ -66,8 +66,8 @@ public static RegistryAdapter create(ReflectionRegistry registry, ProxyRegistry } @Override - public void registerType(ConfigurationCondition condition, Class type) { - registry.register(condition, type); + public void registerType(ConfigurationCondition condition, Class type, boolean registerMetadata) { + registry.register(condition, registerMetadata, type); } @Override 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 5b6941dc6bf0..214d7217102d 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 @@ -64,9 +64,11 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.stream.Collectors; +import com.oracle.svm.core.jdk.UninterruptibleUtils; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeProxyCreation; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -501,6 +503,8 @@ public void registerMethodLookup(ConfigurationCondition condition, Class decl } catch (NoSuchMethodException e) { negativeMethodLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()) .add(new AnalysisMethod.Signature(methodName, metaAccess.lookupJavaTypes(parameterTypes))); + } catch (LinkageError le) { + registerLinkageError(declaringClass, le, methodLookupExceptions); } }); } @@ -514,6 +518,8 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class } catch (NoSuchMethodException e) { negativeConstructorLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()) .add(metaAccess.lookupJavaTypes(parameterTypes)); + } catch (LinkageError le) { + registerLinkageError(declaringClass, le, constructorLookupExceptions); } }); } @@ -525,7 +531,7 @@ public void register(ConfigurationCondition condition, boolean finalIsWritable, } @Override - public void registerAllFieldsQuery(ConfigurationCondition condition, Class clazz) { + public void registerAllFields(ConfigurationCondition condition, Class clazz) { registerAllFieldsQuery(condition, false, clazz); } @@ -544,7 +550,7 @@ public void registerAllFieldsQuery(ConfigurationCondition condition, boolean que } @Override - public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class clazz) { + public void registerAllDeclaredFields(ConfigurationCondition condition, Class clazz) { registerAllDeclaredFieldsQuery(condition, false, clazz); } @@ -575,16 +581,13 @@ private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Fiel var classFields = registeredFields.computeIfAbsent(declaringClass, t -> new ConcurrentHashMap<>()); boolean exists = classFields.containsKey(analysisField); + // TODO open a ticket to disallow boolean shouldRegisterReachabilityHandler = classFields.isEmpty(); var cndValue = classFields.computeIfAbsent(analysisField, f -> new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), reflectField)); - if (!queriedOnly) { - /* queryOnly methods are conditioned by the type itself */ - cndValue.getConditions().addCondition(cnd); - } - if (!exists) { - registerTypesForField(analysisField, reflectField, true); + registerTypesForField(analysisField, reflectField, queriedOnly); + // TODO bulk it up /* * The image needs to know about subtypes shadowing fields registered for reflection to * ensure the correctness of run-time reflection queries. @@ -611,10 +614,12 @@ private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Fiel } /* - * We need to run this even if the method has already been registered, in case it was only + * We need to run this even if the field has already been registered, in case it was only * registered as queried. */ if (!queriedOnly) { + /* queryOnly methods are conditioned on the type itself */ + cndValue.getConditions().addCondition(cnd); registerTypesForField(analysisField, reflectField, false); } } @@ -631,6 +636,8 @@ public void registerFieldLookup(ConfigurationCondition condition, Class decla * not necessary. */ negativeFieldLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()).add(fieldName); + } catch (LinkageError le) { + registerLinkageError(declaringClass, le, fieldLookupExceptions); } }); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java index 4335f139eb23..addd1f6fc520 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java @@ -50,6 +50,10 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import com.oracle.svm.core.MissingRegistrationSupport; +import com.oracle.svm.core.MissingRegistrationUtils; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -520,7 +524,8 @@ private void registerForSerialization(ConfigurationCondition cnd, Class seria * serialization class consistency, so need to register all constructors, methods and * fields. */ - registerSerializationUIDElements(serializationTargetClass, true); // if MRE + boolean legacyFullyRegister = !MissingRegistrationSupport.singleton().reportMissingRegistrationErrors(serializationTargetClass); + registerSerializationUIDElements(serializationTargetClass, legacyFullyRegister); /* * Required by jdk.internal.reflect.ReflectionFactory.newConstructorForSerialization @@ -543,11 +548,17 @@ private void registerForSerialization(ConfigurationCondition cnd, Class seria Class iter = serializationTargetClass; while (iter != null) { - Arrays.stream(iter.getDeclaredFields()).map(Field::getType).forEach(type -> { - RuntimeReflection.registerAllDeclaredMethods(type); - RuntimeReflection.registerAllDeclaredFields(type); - RuntimeReflection.registerAllDeclaredConstructors(type); - }); + // TODO linkage error + Arrays.stream(ReflectionUtil.linkageSafeQuery(iter, new Field[0], Class::getDeclaredFields)) + .map(Field::getType).forEach(type -> { + if (legacyFullyRegister) { + RuntimeReflection.registerAllDeclaredMethods(type); + RuntimeReflection.registerAllDeclaredFields(type); + RuntimeReflection.registerAllDeclaredConstructors(type); + } else { + RuntimeReflection.register(type); + } + }); iter = iter.getSuperclass(); } } @@ -566,11 +577,12 @@ static void registerSerializationUIDElements(Class serializationTargetClass, RuntimeReflection.registerAllDeclaredFields(serializationTargetClass); if (fullyRegister) { /* This is here a legacy that we can't remove as it is a breaking change */ - RuntimeReflection.register(serializationTargetClass.getDeclaredConstructors()); - RuntimeReflection.register(serializationTargetClass.getDeclaredMethods()); - RuntimeReflection.register(serializationTargetClass.getDeclaredFields()); + RuntimeReflection.register(ReflectionUtil.linkageSafeQuery(serializationTargetClass, new Constructor[0], Class::getDeclaredConstructors)); + RuntimeReflection.register(ReflectionUtil.linkageSafeQuery(serializationTargetClass, new Method[0], Class::getDeclaredMethods)); + RuntimeReflection.register(ReflectionUtil.linkageSafeQuery(serializationTargetClass, new Field[0], Class::getDeclaredFields)); + + RuntimeReflection.registerFieldLookup(serializationTargetClass, "serialPersistentFields"); } - RuntimeReflection.registerFieldLookup(serializationTargetClass, "serialPersistentFields"); } public void afterAnalysis() { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java index 8ae0b0b2225f..5e8c0e7e1b41 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java @@ -36,6 +36,7 @@ import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.TargetClass; +import jdk.graal.compiler.api.replacements.Fold; import jdk.vm.ci.meta.ResolvedJavaType; /** @@ -74,6 +75,8 @@ public static boolean shouldExclude(Executable method, AnalysisMetaAccess metaAc return true; } else if (aMethod.isAnnotationPresent(Delete.class)) { return true; // accesses would fail at runtime + } else if (aMethod.isAnnotationPresent(Fold.class)) { + return true; // accesses can contain hosted elements } else if (aMethod.isSynthetic() && aMethod.getDeclaringClass().isAnnotationPresent(TargetClass.class)) { /* * Synthetic methods are usually methods injected by javac to provide access to diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java index f8a8c3c237f5..7b39d977c7aa 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java @@ -30,6 +30,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.function.Function; /** * This class contains utility methods for commonly used reflection functionality. Note that lookups @@ -86,7 +87,7 @@ public static Method lookupMethod(boolean optional, Class declaringClass, Str openModule(declaringClass); result.setAccessible(true); return result; - } catch (ReflectiveOperationException ex) { + } catch (ReflectiveOperationException | LinkageError ex) { if (optional) { return null; } @@ -238,4 +239,17 @@ public static void writeField(Class declaringClass, String fieldName, Object public static void writeStaticField(Class declaringClass, String fieldName, Object value) { writeField(declaringClass, fieldName, null, value); } + + /** + * Query a class value without worrying if the class can be properly linked. + */ + public static T linkageSafeQuery(Class clazz, T defaultValue, Function, T> f) { + var result = defaultValue; + try { + result = f.apply(clazz); + } catch (LinkageError e) { + /* nothing we can do */ + } + return result; + } } diff --git a/vm/mx.vm/mx_vm_benchmark.py b/vm/mx.vm/mx_vm_benchmark.py index bc2eece651ca..a0047eef9f52 100644 --- a/vm/mx.vm/mx_vm_benchmark.py +++ b/vm/mx.vm/mx_vm_benchmark.py @@ -238,10 +238,11 @@ def __init__(self, vm: NativeImageVM, bm_suite: BenchmarkSuite | NativeImageBenc base_image_build_args += ['--gc=' + vm.gc] + ['-H:+SpawnIsolates'] if vm.native_architecture: base_image_build_args += ['-march=native'] + if vm.dynamic_access: + base_image_build_args += ['-H:+EnableDynamicAccess', '-J-XX:+PrintGC', '--exact-reachability-metadata', '--parallelism=12', + '--initialize-at-build-time=com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory$CollectionMapping', '--initialize-at-build-time=com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory$ArrayMapping'] if vm.analysis_context_sensitivity: base_image_build_args += ['-H:AnalysisContextSensitivity=' + vm.analysis_context_sensitivity, '-H:-RemoveSaturatedTypeFlows', '-H:+AliasArrayTypeFlows'] - if vm.no_inlining_before_analysis: - base_image_build_args += ['-H:-InlineBeforeAnalysis'] if vm.optimization_level: base_image_build_args += ['-' + vm.optimization_level] if vm.async_sampler: @@ -531,6 +532,7 @@ def __init__(self, name, config_name, extra_java_args=None, extra_launcher_args= self.is_llvm = False self.gc = None self.native_architecture = False + self.dynamic_access = False self.use_upx = False self.use_open_type_world = False self.use_compacting_gc = False @@ -546,7 +548,6 @@ def __init__(self, name, config_name, extra_java_args=None, extra_launcher_args= self.force_profile_inference = False self.profile_inference_debug = False self.analysis_context_sensitivity = None - self.no_inlining_before_analysis = False self.optimization_level = None self._configure_comma_separated_configs(config_name) if ',' in config_name: @@ -568,6 +569,8 @@ def config_name(self): config = [] if self.native_architecture is True: config += ["native-architecture"] + if self.dynamic_access is True: + config += ["dynamic-access"] if self.use_string_inlining is True: config += ["string-inlining"] if self.use_open_type_world is True: @@ -605,8 +608,6 @@ def config_name(self): if sensitivity.startswith("_"): sensitivity = sensitivity[1:] config += [sensitivity] - if self.no_inlining_before_analysis is True: - config += ["no-inline"] if self.jdk_profiles_collect is True: config += ["jdk-profiles-collect"] if self.adopted_jdk_pgo is True: @@ -642,7 +643,8 @@ def _configure_from_name(self, config_name): mx.abort(f"config_name must be set. Use 'default' for the default {self.__class__.__name__} configuration.") # This defines the allowed config names for NativeImageVM. The ones registered will be available via --jvm-config - rule = r'^(?Pnative-architecture-)?(?Pstring-inlining-)?(?Potw-)?(?Pcompacting-gc-)?(?Pgate-)?(?Pupx-)?(?Pquickbuild-)?(?Pg1gc-)?' \ + rule = r'^(?Pnative-architecture-)?(?Pstring-inlining-)?(?Potw-)?(?Pcompacting-gc-)?(?Pdynamic-access-)?' \ + r'(?Pgate-)?(?Pupx-)?(?Pquickbuild-)?(?Pg1gc-)?' \ r'(?Pllvm-)?(?Ppgo-|pgo-ctx-insens-|pgo-sampler-)?(?Pinline-)?' \ r'(?Pinsens-|allocsens-|1obj-|2obj1h-|3obj2h-|4obj3h-)?(?Pno-inline-)?(?Pjdk-profiles-collect-|adopted-jdk-pgo-)?' \ r'(?Pprofile-inference-feature-extraction-|profile-inference-pgo-|profile-inference-debug-)?(?Psafepoint-sampler-|async-sampler-)?(?PO0-|O1-|O2-|O3-|Os-)?(default-)?(?Pce-|ee-)?$' @@ -657,6 +659,10 @@ def _configure_from_name(self, config_name): mx.logv(f"'native-architecture' is enabled for {config_name}") self.native_architecture = True + if matching.group("dynamic_access") is not None: + mx.logv(f"'dynamic-access' is enabled for {config_name}") + self.dynamic_access = True + if matching.group("string_inlining") is not None: mx.logv(f"'string-inlining' is enabled for {config_name}") self.use_string_inlining = True @@ -796,14 +802,6 @@ def generate_profiling_package_prefixes(): else: mx.abort(f"Unknown configuration for optimization level: {olevel}") - if matching.group("no_inlining_before_analysis") is not None: - option = matching.group("no_inlining_before_analysis")[:-1] - if option == "no-inline": - mx.logv(f"not doing inlining before analysis for {config_name}") - self.no_inlining_before_analysis = True - else: - mx.abort(f"Unknown configuration for no inlining before analysis: {option}") - if matching.group("analysis_context_sensitivity") is not None: context_sensitivity = matching.group("analysis_context_sensitivity")[:-1] if context_sensitivity in ["insens", "allocsens"]: @@ -1938,18 +1936,15 @@ def register_graalvm_vms(): mx_polybenchmarks_benchmark.rules = polybenchmark_rules optimization_levels = ['O0', 'O1', 'O2', 'O3', 'Os'] - - # Inlining before analysis is done by default analysis_context_sensitivity = ['insens', 'allocsens', '1obj', '2obj1h', '3obj2h', '4obj3h'] - analysis_context_sensitivity_no_inline = [f"{analysis_component}-no-inline" for analysis_component in analysis_context_sensitivity] for short_name, config_suffix in [('niee', 'ee'), ('ni', 'ce')]: if any(component.short_name == short_name for component in mx_sdk_vm_impl.registered_graalvm_components(stage1=False)): - for main_config in ['default', 'gate', 'llvm', 'native-architecture'] + analysis_context_sensitivity + analysis_context_sensitivity_no_inline: + for main_config in ['default', 'gate', 'llvm', 'native-architecture', 'dynamic-access'] + analysis_context_sensitivity: final_config_name = f'{main_config}-{config_suffix}' mx_benchmark.add_java_vm(NativeImageVM('native-image', final_config_name, ['--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED']), _suite, 10) # ' ' force the empty O<> configs as well - for main_config in ['llvm', 'native-architecture', 'g1gc', 'native-architecture-g1gc', ''] + analysis_context_sensitivity + analysis_context_sensitivity_no_inline: + for main_config in ['llvm', 'native-architecture', 'g1gc', 'native-architecture-g1gc', 'dynamic-access', ''] + analysis_context_sensitivity: for optimization_level in optimization_levels: if len(main_config) > 0: final_config_name = f'{main_config}-{optimization_level}-{config_suffix}'