diff --git a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/PointsToAnalyzer.java b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/PointsToAnalyzer.java index 0b72b4f71948..35b37f83cc31 100644 --- a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/PointsToAnalyzer.java +++ b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/PointsToAnalyzer.java @@ -214,6 +214,7 @@ private PointsToAnalyzer(String mainEntryClass, OptionValues options) { PointstoGraphBuilderPlugins.registerSystemPlugins(plugins.getInvocationPlugins()); PointstoGraphBuilderPlugins.registerObjectPlugins(plugins.getInvocationPlugins()); } + bigbang.markInitializationFinished(); } private String getAnalysisName(String entryClass) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java index 9182cad59297..a74490a6dc44 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java @@ -103,6 +103,7 @@ public abstract class AbstractAnalysisEngine implements BigBang { protected final ClassInclusionPolicy classInclusionPolicy; private static final ResolvedJavaMethod[] NO_METHODS = new ResolvedJavaMethod[]{}; private static final ResolvedJavaField[] NO_FIELDS = new ResolvedJavaField[]{}; + private volatile boolean initialized = false; @SuppressWarnings("this-escape") public AbstractAnalysisEngine(OptionValues options, AnalysisUniverse universe, HostVM hostVM, AnalysisMetaAccess metaAccess, SnippetReflectionProvider snippetReflectionProvider, @@ -226,6 +227,19 @@ private boolean analysisModified() { return analysisModified; } + @Override + public void markInitializationFinished() { + assert !initialized; + + initialized = true; + universe.notifyBigBangInitialized(); + } + + @Override + public boolean isInitialized() { + return initialized; + } + @Override public void cleanupAfterAnalysis() { universe.getTypes().forEach(AnalysisType::cleanupAfterAnalysis); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java index df43a639476c..8c7fd5de64d3 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java @@ -128,6 +128,10 @@ default void onTypeReachable(AnalysisType type) { void initializeMetaData(AnalysisType type); + void markInitializationFinished(); + + boolean isInitialized(); + /** * Callback executed after the analysis finished. The cleanupAfterAnalysis is executed after the * universe builder, which can be too late for some tasks. 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 6df2860d1b59..733c830e00bc 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 @@ -662,6 +662,7 @@ public TypeFlow getTypeFlow() { @SuppressWarnings("try") @Override public boolean finish() throws InterruptedException { + assert isInitialized(); try (Indent indent = debug.logAndIndent("starting analysis in BigBang.finish")) { boolean didSomeWork = false; do { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java index 511a911ef8ed..b508f6b0ca50 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java @@ -30,7 +30,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -171,7 +170,6 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav private final AnalysisType[] interfaces; private AnalysisMethod[] declaredMethods; - private Set dispatchTableMethods; /* isArray is an expensive operation so we eagerly compute it */ private final boolean isArray; @@ -1389,63 +1387,6 @@ public AnalysisMethod[] getDeclaredConstructors(boolean forceLink) { return universe.lookup(wrapped.getDeclaredConstructors(forceLink)); } - public boolean isOpenTypeWorldDispatchTableMethodsCalculated() { - return dispatchTableMethods != null; - } - - public Set getOpenTypeWorldDispatchTableMethods() { - Objects.requireNonNull(dispatchTableMethods); - return dispatchTableMethods; - } - - /* - * Calculates all methods in this class which should be included in its dispatch table. - */ - public Set getOrCalculateOpenTypeWorldDispatchTableMethods() { - if (dispatchTableMethods != null) { - return dispatchTableMethods; - } - if (isPrimitive()) { - dispatchTableMethods = Set.of(); - return dispatchTableMethods; - } - if (getWrapped() instanceof BaseLayerType) { - // GR-58587 implement proper support. - dispatchTableMethods = Set.of(); - return dispatchTableMethods; - } - - var resultSet = new HashSet(); - for (ResolvedJavaMethod m : getWrapped().getDeclaredMethods(false)) { - assert !m.isConstructor() : Assertions.errorMessage("Unexpected constructor", m); - if (m.isStatic()) { - /* Only looking at member methods */ - continue; - } - try { - AnalysisMethod aMethod = universe.lookup(m); - assert aMethod != null : m; - resultSet.add(aMethod); - } catch (UnsupportedFeatureException t) { - /* - * Methods which are deleted or not available on this platform will throw an error - * during lookup - ignore and continue execution - * - * Note it is not simple to create a check to determine whether calling - * universe#lookup will trigger an error by creating an analysis object for a type - * not supported on this platform, as creating a method requires, in addition to the - * types of its return type and parameters, all of the super types of its return and - * parameters to be created as well. - */ - } - } - - // ensure result is fully visible across threads - VarHandle.storeStoreFence(); - dispatchTableMethods = resultSet; - return dispatchTableMethods; - } - @Override public AnalysisMethod findMethod(String name, Signature signature) { for (AnalysisMethod method : getDeclaredMethods(false)) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java index 96ea87f8980b..c1072f908ab6 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java @@ -34,6 +34,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Function; import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; @@ -106,6 +107,7 @@ public class AnalysisUniverse implements Universe { private Function[] objectReplacers; private Function[] objectToConstantReplacers; + private Consumer[] onTypeCreatedCallbacks; private SubstitutionProcessor[] featureSubstitutions; private SubstitutionProcessor[] featureNativeSubstitutions; @@ -147,6 +149,7 @@ public AnalysisUniverse(HostVM hostVM, JavaKind wordKind, AnalysisPolicy analysi sealed = false; objectReplacers = (Function[]) new Function[0]; objectToConstantReplacers = (Function[]) new Function[0]; + onTypeCreatedCallbacks = (Consumer[]) new Consumer[0]; featureSubstitutions = new SubstitutionProcessor[0]; featureNativeSubstitutions = new SubstitutionProcessor[0]; unsafeAccessedStaticFields = analysisPolicy.useConservativeUnsafeAccess() ? null : new ConcurrentHashMap<>(); @@ -327,6 +330,12 @@ private AnalysisType createType(ResolvedJavaType type) { assert oldValue == claim : oldValue + " != " + claim; claim = null; + /* + * Trigger type creation callbacks. Note this will run in parallel with other threads + * being able to retrieve this AnalysisType from {@code types}. + */ + runOnTypeCreatedCallbacks(newValue); + return newValue; } finally { @@ -621,6 +630,13 @@ public void registerObjectToConstantReplacer(Function objectToConstantReplacers[objectToConstantReplacers.length - 1] = replacer; } + public void registerOnTypeCreatedCallback(Consumer consumer) { + assert consumer != null; + assert !bb.isInitialized() : "too late to add a callback"; + onTypeCreatedCallbacks = Arrays.copyOf(onTypeCreatedCallbacks, onTypeCreatedCallbacks.length + 1); + onTypeCreatedCallbacks[onTypeCreatedCallbacks.length - 1] = consumer; + } + public void registerFeatureSubstitution(SubstitutionProcessor substitution) { SubstitutionProcessor[] subs = featureSubstitutions; subs = Arrays.copyOf(subs, subs.length + 1); @@ -699,6 +715,40 @@ private Object replaceObject0(Object source, boolean allowObjectToConstantReplac return ihc == null ? destination : ihc; } + public void notifyBigBangInitialized() { + assert bb.isInitialized(); + + /* + * It is possible for types to be created before all typeCreationCallbacks are installed. + * Hence, we trigger the typeCreationCallbacks for all types created prior to the completion + * of big bang initialization at this point. + */ + for (var obj : types.values().toArray()) { + /* + * Nominally the map values are of type object and can hold a thread object while an + * AnalysisType is being created. However, this method is called when all values will be + * of type AnalysisType. + */ + AnalysisType aType = (AnalysisType) obj; + runOnTypeCreatedCallbacks(aType); + } + } + + private void runOnTypeCreatedCallbacks(AnalysisType type) { + if (bb == null || !bb.isInitialized()) { + /* + * Until the big bang is initialized, it is possible for more callbacks to be + * registered. Hence, these hooks are run on all types created before big bang + * initialization via {@code notifyBigBangInitialized} + */ + return; + } + + for (var callback : onTypeCreatedCallbacks) { + bb.postTask((t) -> callback.accept(type)); + } + } + public void registerOverrideReachabilityNotification(AnalysisMethod declaredMethod, MethodOverrideReachableNotification notification) { methodOverrideReachableNotifications.computeIfAbsent(declaredMethod, m -> ConcurrentHashMap.newKeySet()).add(notification); } diff --git a/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java b/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java index 909d65b3dd51..022cf5696627 100644 --- a/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java +++ b/substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java @@ -284,6 +284,7 @@ public void markMethodInvoked(ReachabilityAnalysisMethod method, Object reason) @Override public boolean finish() throws InterruptedException { + assert isInitialized(); do { runReachability(); assert executor.getPostedOperations() == 0 : executor.getPostedOperations(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java index a9a6a4f5e14d..40c22ce35565 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java @@ -312,6 +312,15 @@ public void registerObjectToConstantReplacer(Function getUniverse().registerObjectToConstantReplacer(replacer); } + /** + * Register a callback which will execute once for each analysis type created. Note this + * callback runs when the created AnalysisType is already visible within the analysis + * universe. + */ + public void registerOnTypeCreatedCallback(Consumer callback) { + getUniverse().registerOnTypeCreatedCallback(callback); + } + /** * Register a callback that is executed when an object of the specified type or any of its * subtypes is marked as reachable. The callback is executed before the object is added to diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedStaticFieldSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedStaticFieldSupportImpl.java index 732db419fbc7..7b6a526cbcd2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedStaticFieldSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedStaticFieldSupportImpl.java @@ -136,7 +136,7 @@ public int getInstalledLayerNum(ResolvedJavaField field) { } else { AnalysisField aField = (AnalysisField) field; return switch (LayeredStaticFieldSupport.singleton().getAssignmentStatus(aField)) { - case UNDECIDED -> getCurrentLayerNumber(); + case UNSPECIFIED -> getCurrentLayerNumber(); case PRIOR_LAYER -> LayeredStaticFieldSupport.singleton().getPriorInstalledLayerNum(aField); case APP_LAYER_REQUESTED, APP_LAYER_DEFERRED -> LayeredStaticFieldSupport.getAppLayerNumber(); }; 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 c31c6e7c4597..caf7ff435e4d 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 @@ -817,9 +817,9 @@ protected boolean runPointsToAnalysis(String imageName, OptionValues options, De bb.getHostVM().getClassInitializationSupport().sealConfiguration(); if (ImageLayerBuildingSupport.buildingImageLayer()) { ImageSingletons.lookup(LoadImageSingletonFeature.class).processRegisteredSingletons(aUniverse); - } - if (ImageLayerBuildingSupport.buildingSharedLayer()) { - HostedImageLayerBuildingSupport.singleton().getWriter().initializeExternalValues(); + if (ImageLayerBuildingSupport.buildingSharedLayer()) { + HostedImageLayerBuildingSupport.singleton().getWriter().initializeExternalValues(); + } } } @@ -1252,6 +1252,8 @@ public static void initializeBigBang(Inflation bb, OptionValues options, Feature performSnippetGraphAnalysis(bb, aReplacements, options, Function.identity()); } + + bb.markInitializationFinished(); } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java index f4fae124375e..6035b35c9c5b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/OpenTypeWorldFeature.java @@ -29,13 +29,17 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.BaseLayerType; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; @@ -61,10 +65,13 @@ import com.oracle.svm.hosted.meta.TypeCheckBuilder; import jdk.graal.compiler.debug.Assertions; +import jdk.vm.ci.meta.ResolvedJavaMethod; @AutomaticallyRegisteredFeature public class OpenTypeWorldFeature implements InternalFeature { + private Map> typeToDispatchTableMethods; + @Override public boolean isInConfiguration(Feature.IsInConfigurationAccess access) { return !SubstrateOptions.useClosedTypeWorldHubLayout(); @@ -77,21 +84,16 @@ public void beforeUniverseBuilding(BeforeUniverseBuildingAccess access) { } } - private final Set triggeredTypes = new HashSet<>(); - @Override - public void duringAnalysis(DuringAnalysisAccess access) { - var config = (FeatureImpl.DuringAnalysisAccessImpl) access; - for (AnalysisType aType : config.getUniverse().getTypes()) { - if (triggeredTypes.add(aType)) { - aType.getOrCalculateOpenTypeWorldDispatchTableMethods(); - config.requireAnalysisIteration(); - } - } + public void duringSetup(DuringSetupAccess access) { + FeatureImpl.DuringSetupAccessImpl config = (FeatureImpl.DuringSetupAccessImpl) access; + config.registerOnTypeCreatedCallback(this::calculateOpenTypeWorldDispatchTableMethods); + typeToDispatchTableMethods = new ConcurrentHashMap<>(); } @Override public void beforeCompilation(BeforeCompilationAccess access) { + typeToDispatchTableMethods = null; var impl = (FeatureImpl.BeforeCompilationAccessImpl) access; for (HostedType type : impl.getUniverse().getTypes()) { DynamicHub hub = type.getHub(); @@ -100,10 +102,70 @@ public void beforeCompilation(BeforeCompilationAccess access) { } } + public static OpenTypeWorldFeature singleton() { + return ImageSingletons.lookup(OpenTypeWorldFeature.class); + } + + public boolean isOpenTypeWorldDispatchTableMethodsCalculated(AnalysisType aType) { + return typeToDispatchTableMethods.containsKey(aType); + } + + public Set getOpenTypeWorldDispatchTableMethods(AnalysisType aType) { + var result = typeToDispatchTableMethods.get(aType); + return Objects.requireNonNull(result); + } + + private void calculateOpenTypeWorldDispatchTableMethods(AnalysisType aType) { + var result = calculateOpenTypeWorldDispatchTableMethods0(aType); + Objects.requireNonNull(result); + var prev = typeToDispatchTableMethods.put(aType, result); + assert prev == null : prev; + } + + /* + * Calculates all methods in this class which should be included in its dispatch table. + */ + private static Set calculateOpenTypeWorldDispatchTableMethods0(AnalysisType aType) { + if (aType.isPrimitive()) { + return Set.of(); + } + if (aType.getWrapped() instanceof BaseLayerType) { + // GR-58587 implement proper support. + return Set.of(); + } + + var resultSet = new HashSet(); + for (ResolvedJavaMethod m : aType.getWrapped().getDeclaredMethods(false)) { + assert !m.isConstructor() : Assertions.errorMessage("Unexpected constructor", m); + if (m.isStatic()) { + /* Only looking at member methods */ + continue; + } + try { + AnalysisMethod aMethod = aType.getUniverse().lookup(m); + assert aMethod != null : m; + resultSet.add(aMethod); + } catch (UnsupportedFeatureException t) { + /* + * Methods which are deleted or not available on this platform will throw an error + * during lookup - ignore and continue execution + * + * Note it is not simple to create a check to determine whether calling + * universe#lookup will trigger an error by creating an analysis object for a type + * not supported on this platform, as creating a method requires, in addition to the + * types of its return type and parameters, all of the super types of its return and + * parameters to be created as well. + */ + } + } + + return resultSet; + } + /** * see {@link SharedMethod#getIndirectCallTarget}. */ - public static void computeIndirectCallTargets(HostedUniverse hUniverse, Map methods) { + public void computeIndirectCallTargets(HostedUniverse hUniverse, Map methods) { Map allInterfacesMap = new HashMap<>(); methods.forEach((aMethod, hMethod) -> { assert aMethod.isOriginalMethod(); @@ -127,7 +189,7 @@ public static void computeIndirectCallTargets(HostedUniverse hUniverse, Map allInterfacesMap, HostedMethod hOriginal) { + private AnalysisMethod calculateIndirectCallTarget(Map allInterfacesMap, HostedMethod hOriginal) { AnalysisMethod aOriginal = hOriginal.getWrapped(); if (hOriginal.isStatic() || hOriginal.isConstructor()) { /* @@ -137,7 +199,7 @@ private static AnalysisMethod calculateIndirectCallTarget(Map processedAppLayerDeferredClassFilters; + AnalysisType byteArrayType; + AnalysisType objectArrayType; + Set appLayerDeferredClassFilters; + LayeredStaticFieldSupport() { this(ConcurrentHashMap.newKeySet(), new UniverseBuilder.StaticFieldOffsets()); } @@ -110,6 +123,7 @@ private LayeredStaticFieldSupport(Set appLayerFields, UniverseBuilder.St priorInstalledLayerMap = inAppLayer ? new ConcurrentHashMap<>() : null; priorInstalledLocationMap = inAppLayer ? new ConcurrentHashMap<>() : null; this.appLayerStaticFieldOffsets = appLayerStaticFieldOffsets; + processedAppLayerDeferredClassFilters = inAppLayer ? null : ConcurrentHashMap.newKeySet(); } /** @@ -117,9 +131,9 @@ private LayeredStaticFieldSupport(Set appLayerFields, UniverseBuilder.St */ public enum LayerAssignmentStatus { /** - * This field has yet to be assigned a layer. + * This field has not been explicitly assigned to a layer. Proceed with normal behavior. */ - UNDECIDED, + UNSPECIFIED, /** * Was installed in a prior layer. */ @@ -163,11 +177,18 @@ public void ensureInitializedFromFieldData(AnalysisField aField, SharedLayerSnap } private void installFieldInAppLayer(Field field, MetaAccessProvider meta) { + AnalysisField aField = (AnalysisField) meta.lookupJavaField(field); + installFieldInAppLayer(aField); + } + + void installFieldInAppLayer(AnalysisField aField) { assert !inAppLayer && !BuildPhaseProvider.isAnalysisFinished(); - AnalysisField aField = (AnalysisField) meta.lookupJavaField(field); var added = appLayerFields.add(aField); - assert added; + if (!added) { + /* If this field has already been added, then nothing needs to be done. */ + return; + } /* * Trigger build-time initialization of the class if it was not already initialized (and the @@ -179,14 +200,14 @@ private void installFieldInAppLayer(Field field, MetaAccessProvider meta) { // register this field as requiring app layer var previous = assignmentStatusMap.put(aField, LayerAssignmentStatus.APP_LAYER_REQUESTED); if (previous != null) { - throw AnalysisError.userError(String.format("Field has a prior assignment. This is due to registering an app layer deferred field too late. Field: %s. Previous: %s", field, previous)); + throw AnalysisError.userError(String.format("Field has a prior assignment. This is due to registering an app layer deferred field too late. Field: %s. Previous: %s", aField, previous)); } // ensure the appropriate future layer constant exists var registry = CrossLayerConstantRegistry.singletonOrNull(); - if (field.getType().isPrimitive()) { + if (aField.getStorageKind().isPrimitive()) { if (appLayerPrimitiveStaticFieldsBase == null) { - AnalysisType futureType = (AnalysisType) meta.lookupJavaType(byte[].class); + AnalysisType futureType = byteArrayType; synchronized (this) { if (appLayerPrimitiveStaticFieldsBase == null) { appLayerPrimitiveStaticFieldsBase = (ImageHeapRelocatableConstant) registry.registerFutureHeapConstant(appLayerPrimitiveStaticFieldsBaseName, futureType); @@ -195,7 +216,7 @@ private void installFieldInAppLayer(Field field, MetaAccessProvider meta) { } } } else if (appLayerObjectStaticFieldsBase == null) { - AnalysisType futureType = (AnalysisType) meta.lookupJavaType(Object[].class); + AnalysisType futureType = objectArrayType; synchronized (this) { if (appLayerObjectStaticFieldsBase == null) { appLayerObjectStaticFieldsBase = (ImageHeapRelocatableConstant) registry.registerFutureHeapConstant(appLayerObjectStaticFieldsBaseName, futureType); @@ -214,12 +235,35 @@ void initializeClassInAppLayer(Class c, MetaAccessProvider meta) { } } - public LayerAssignmentStatus getAssignmentStatus(AnalysisField analysisField) { - return assignmentStatusMap.computeIfAbsent(analysisField, _ -> { - if (!(inAppLayer && analysisField.isInBaseLayer())) { - return LayerAssignmentStatus.UNDECIDED; + void processAppLayerDeferredClassFilters(AnalysisType aType) { + if (!processedAppLayerDeferredClassFilters.contains(aType)) { + if (appLayerDeferredClassFilters.contains(aType.toJavaName())) { + for (ResolvedJavaField aField : aType.getStaticFields()) { + installFieldInAppLayer((AnalysisField) aField); + } + } + + /* + * Intentionally wait until the end to set the type as processed to ensure all fields + * are registered before a thread can return from this method. + */ + processedAppLayerDeferredClassFilters.add(aType); + } + } + + public LayerAssignmentStatus getAssignmentStatus(AnalysisField aField) { + if (!inAppLayer) { + LayerAssignmentStatus status = assignmentStatusMap.get(aField); + if (status != null) { + return status; + } + processAppLayerDeferredClassFilters(aField.getDeclaringClass()); + } + return assignmentStatusMap.computeIfAbsent(aField, _ -> { + if (!(inAppLayer && aField.isInBaseLayer())) { + return LayerAssignmentStatus.UNSPECIFIED; } - throw VMError.shouldNotReachHere(String.format("Base analysis field assignment status queried before it is initialized: %s", analysisField)); + throw VMError.shouldNotReachHere(String.format("Base analysis field assignment status queried before it is initialized: %s", aField)); }); } @@ -235,7 +279,7 @@ public int getPriorInstalledLayerNum(AnalysisField analysisField) { public boolean preventConstantFolding(AnalysisField aField) { var state = getAssignmentStatus(aField); return switch (state) { - case UNDECIDED, PRIOR_LAYER -> false; + case UNSPECIFIED, PRIOR_LAYER -> false; case APP_LAYER_REQUESTED, APP_LAYER_DEFERRED -> !inAppLayer; }; } @@ -243,7 +287,7 @@ public boolean preventConstantFolding(AnalysisField aField) { public boolean installableInLayer(AnalysisField aField) { var state = getAssignmentStatus(aField); return switch (state) { - case UNDECIDED -> { + case UNSPECIFIED -> { assert getPriorInstalledLayerNum(aField) == MultiLayeredImageSingleton.LAYER_NUM_UNINSTALLED; yield true; } @@ -284,7 +328,7 @@ public boolean skipStaticField(HostedField field, Function AnalysisField aField = field.getWrapped(); LayerAssignmentStatus state = getAssignmentStatus(aField); return switch (state) { - case UNDECIDED -> traditionalSkipFieldLogic.apply(field); + case UNSPECIFIED -> traditionalSkipFieldLogic.apply(field); /* This field's location has already been decided. */ case PRIOR_LAYER -> false; case APP_LAYER_REQUESTED -> { @@ -402,16 +446,20 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { return ImageLayerBuildingSupport.buildingInitialLayer(); } - /** - * Register all application layered fields declared via the commandline. - */ @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { - var config = (FeatureImpl.BeforeAnalysisAccessImpl) access; - var metaAccess = config.getMetaAccess(); - for (String className : SubstrateOptions.ApplicationLayerInitializedClasses.getValue().values()) { - LayeredClassInitialization.singleton().initializeClassInAppLayer(config.getImageClassLoader().findClassOrFail(className), metaAccess); - } + public void duringSetup(DuringSetupAccess access) { + FeatureImpl.DuringSetupAccessImpl config = (FeatureImpl.DuringSetupAccessImpl) access; + AnalysisMetaAccess metaAccess = config.getMetaAccess(); + LayeredStaticFieldSupport staticFieldSupport = LayeredStaticFieldSupport.singleton(); + + staticFieldSupport.objectArrayType = metaAccess.lookupJavaType(Object[].class); + staticFieldSupport.byteArrayType = metaAccess.lookupJavaType(byte[].class); + staticFieldSupport.appLayerDeferredClassFilters = SubstrateOptions.ApplicationLayerInitializedClasses.getValue().valuesAsSet(); + + /* + * Register callback which will run for all created types. + */ + config.registerOnTypeCreatedCallback(staticFieldSupport::processAppLayerDeferredClassFilters); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java index dffe63be6381..cc4b5e30624a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java @@ -723,7 +723,7 @@ private void persistField(AnalysisField field, Supplier { assert aMethod.isOriginalMethod(); @@ -839,7 +839,7 @@ private void layoutStaticFields() { assert currentLayer < layerNum : Assertions.errorMessage(currentLayer, layerNum); offsets = layeredStaticFieldSupport.getFutureLayerOffsets(field, layerNum); } - if (field.getStorageKind() == JavaKind.Object) { + if (field.getStorageKind().isObject()) { field.setLocation(NumUtil.safeToInt(layout.getArrayElementOffset(JavaKind.Object, offsets.nextObjectField)), layerNum); offsets.nextObjectField += 1; } else { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java index 80e2d70417cf..f57408279bc1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/VTableBuilder.java @@ -42,6 +42,7 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.hub.RuntimeClassLoading; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.hosted.OpenTypeWorldFeature; import com.oracle.svm.hosted.imagelayer.LayeredDispatchTableFeature; import jdk.graal.compiler.debug.Assertions; @@ -74,6 +75,7 @@ private static class OpenTypeWorldHubLayoutUtils { private final boolean closedTypeWorld; private final boolean registerTrackedTypes; private final boolean registerAllTypes; + private final OpenTypeWorldFeature otwFeature = OpenTypeWorldFeature.singleton(); OpenTypeWorldHubLayoutUtils(HostedUniverse hUniverse) { closedTypeWorld = SubstrateOptions.useClosedTypeWorld(); @@ -104,7 +106,7 @@ private boolean shouldIncludeType(HostedType type) { * * GR-60010 - We are currently loading base analysis types too late. */ - return type.getWrapped().isOpenTypeWorldDispatchTableMethodsCalculated(); + return otwFeature.isOpenTypeWorldDispatchTableMethodsCalculated(type.getWrapped()); } /* @@ -118,7 +120,7 @@ private boolean shouldIncludeType(HostedType type) { * When using the open type world we are conservative and calculate metadata for all * types seen during analysis. */ - return type.getWrapped().isOpenTypeWorldDispatchTableMethodsCalculated(); + return otwFeature.isOpenTypeWorldDispatchTableMethodsCalculated(type.getWrapped()); } } @@ -217,7 +219,8 @@ private List generateDispatchTable(HostedType type, List