From 7c78d22695bc5eab21aeb0246807140a04825eec Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Wed, 23 Oct 2024 17:26:20 +0200 Subject: [PATCH] Introduce track across layers flag. Used to determine what should be preserved across layers for layered image builds. --- .../com/oracle/graal/pointsto/api/HostVM.java | 4 ++ .../graal/pointsto/heap/ImageLayerWriter.java | 12 ++-- .../graal/pointsto/meta/AnalysisElement.java | 9 ++- .../graal/pointsto/meta/AnalysisField.java | 59 +++++++++++-------- .../graal/pointsto/meta/AnalysisMethod.java | 33 +++++++++-- .../graal/pointsto/meta/AnalysisType.java | 24 +++++++- .../svm/hosted/OpenTypeWorldFeature.java | 14 ++--- .../src/com/oracle/svm/hosted/SVMHost.java | 8 +++ .../svm/hosted/heap/SVMImageLayerWriter.java | 8 ++- .../oracle/svm/hosted/meta/VTableBuilder.java | 12 ++-- 10 files changed, 127 insertions(+), 56 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java index 3f30ff0fefcd..66c9e6479e63 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java @@ -335,6 +335,10 @@ public boolean isClosedTypeWorld() { return true; } + public boolean enableTrackAcrossLayers() { + return false; + } + /** * Helpers to determine what analysis actions should be taken for a given Multi-Method version. */ diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java index 68d939aa2795..cef74b53b90e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java @@ -272,10 +272,6 @@ public void persistImageHeapSize(long imageHeapSize) { jsonMap.put(IMAGE_HEAP_SIZE_TAG, String.valueOf(imageHeapSize)); } - protected boolean shouldPersistMethod(AnalysisMethod method) { - return method.isReachable(); - } - public void persistAnalysisInfo() { persistHook(); @@ -289,18 +285,18 @@ public void persistAnalysisInfo() { * removed after a mechanism for determining which types have to be persisted is added, or * if a stable name is implemented for them. */ - for (AnalysisType type : aUniverse.getTypes().stream().filter(AnalysisType::isReachable).toList()) { + for (AnalysisType type : aUniverse.getTypes().stream().filter(AnalysisType::isTrackedAcrossLayers).toList()) { checkTypeStability(type); persistType(type); } jsonMap.put(TYPES_TAG, typesMap); - for (AnalysisMethod method : aUniverse.getMethods().stream().filter(this::shouldPersistMethod).toList()) { + for (AnalysisMethod method : aUniverse.getMethods().stream().filter(AnalysisMethod::isTrackedAcrossLayers).toList()) { persistMethod(method); } jsonMap.put(METHODS_TAG, methodsMap); - for (AnalysisField field : aUniverse.getFields().stream().filter(AnalysisField::isReachable).toList()) { + for (AnalysisField field : aUniverse.getFields().stream().filter(AnalysisField::isTrackedAcrossLayers).toList()) { persistField(field); } jsonMap.put(FIELDS_TAG, fieldsMap); @@ -448,7 +444,7 @@ public boolean isMethodPersisted(AnalysisMethod method) { public void persistMethodGraphs() { for (AnalysisMethod method : aUniverse.getMethods()) { - if (method.isReachable()) { + if (method.isTrackedAcrossLayers()) { persistAnalysisParsedGraph(method); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisElement.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisElement.java index eb79dc480a93..f1b0a7fe4cd2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisElement.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisElement.java @@ -38,7 +38,6 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; -import jdk.graal.compiler.debug.GraalError; import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; import com.oracle.graal.pointsto.BigBang; @@ -49,6 +48,7 @@ import com.oracle.graal.pointsto.util.AnalysisFuture; import com.oracle.graal.pointsto.util.ConcurrentLightHashSet; +import jdk.graal.compiler.debug.GraalError; import jdk.vm.ci.code.BytecodePosition; import jdk.vm.ci.meta.ModifiersProvider; import jdk.vm.ci.meta.ResolvedJavaField; @@ -112,7 +112,12 @@ protected void notifyReachabilityCallbacks(AnalysisUniverse universe, List isUnsafeAccessedUpdater = AtomicReferenceFieldUpdater .newUpdater(AnalysisField.class, Object.class, "isUnsafeAccessed"); + private static final AtomicReferenceFieldUpdater trackAcrossLayersUpdater = AtomicReferenceFieldUpdater + .newUpdater(AnalysisField.class, Object.class, "trackAcrossLayers"); + private final int id; /** Marks a field loaded from a base layer. */ private final boolean isInBaseLayer; @@ -90,6 +93,12 @@ public abstract class AnalysisField extends AnalysisElement implements WrappedJa @SuppressWarnings("unused") private volatile Object isFolded; @SuppressWarnings("unused") private volatile Object isUnsafeAccessed; + /** + * See {@link AnalysisElement#isTrackedAcrossLayers} for explanation. + */ + @SuppressWarnings("unused") private volatile Object trackAcrossLayers; + private final boolean enableTrackAcrossLayers; + private ConcurrentMap readBy; private ConcurrentMap writtenBy; @@ -150,6 +159,8 @@ public AnalysisField(AnalysisUniverse universe, ResolvedJavaField wrappedField) id = universe.computeNextFieldId(); isInBaseLayer = false; } + + this.enableTrackAcrossLayers = universe.hostVM.enableTrackAcrossLayers(); } @Override @@ -220,13 +231,11 @@ public boolean registerAsAccessed(Object reason) { getDeclaringClass().registerAsReachable(this); assert isValidReason(reason) : "Registering a field as accessed needs to provide a valid reason."; - boolean firstAttempt = AtomicUtils.atomicSet(this, reason, isAccessedUpdater); - if (firstAttempt) { - onReachable(); + return AtomicUtils.atomicSetAndRun(this, reason, isAccessedUpdater, () -> { + onReachable(reason); getUniverse().onFieldAccessed(this); getUniverse().getHeapScanner().onFieldRead(this); - } - return firstAttempt; + }); } /** @@ -236,16 +245,14 @@ public boolean registerAsRead(Object reason) { getDeclaringClass().registerAsReachable(this); assert isValidReason(reason) : "Registering a field as read needs to provide a valid reason."; - boolean firstAttempt = AtomicUtils.atomicSet(this, reason, isReadUpdater); if (readBy != null) { readBy.put(reason, Boolean.TRUE); } - if (firstAttempt) { - onReachable(); + return AtomicUtils.atomicSetAndRun(this, reason, isReadUpdater, () -> { + onReachable(reason); getUniverse().onFieldAccessed(this); getUniverse().getHeapScanner().onFieldRead(this); - } - return firstAttempt; + }); } /** @@ -257,27 +264,25 @@ public boolean registerAsWritten(Object reason) { getDeclaringClass().registerAsReachable(this); assert isValidReason(reason) : "Registering a field as written needs to provide a valid reason."; - boolean firstAttempt = AtomicUtils.atomicSet(this, reason, isWrittenUpdater); - if (writtenBy != null && reason != null) { + if (writtenBy != null) { writtenBy.put(reason, Boolean.TRUE); } - if (firstAttempt) { - onReachable(); + return AtomicUtils.atomicSetAndRun(this, reason, isWrittenUpdater, () -> { + onReachable(reason); if (Modifier.isVolatile(getModifiers()) || getStorageKind() == JavaKind.Object) { getUniverse().onFieldAccessed(this); } - } - return firstAttempt; + }); } public void registerAsFolded(Object reason) { getDeclaringClass().registerAsReachable(this); assert isValidReason(reason) : "Registering a field as folded needs to provide a valid reason."; - if (AtomicUtils.atomicSet(this, reason, isFoldedUpdater)) { + AtomicUtils.atomicSetAndRun(this, reason, isFoldedUpdater, () -> { assert getDeclaringClass().isReachable() : this; - onReachable(); - } + onReachable(reason); + }); } public boolean registerAsUnsafeAccessed(Object reason) { @@ -292,7 +297,7 @@ public boolean registerAsUnsafeAccessed(Object reason) { * only register fields as unsafe accessed with their declaring type once. */ - if (AtomicUtils.atomicSet(this, reason, isUnsafeAccessedUpdater)) { + return AtomicUtils.atomicSetAndRun(this, reason, isUnsafeAccessedUpdater, () -> { /* * The atomic updater ensures that the field is registered as unsafe accessed with its * declaring class only once. However, at the end of this call the registration might @@ -311,9 +316,7 @@ public boolean registerAsUnsafeAccessed(Object reason) { AnalysisType declaringType = getDeclaringClass(); declaringType.registerUnsafeAccessedField(this); } - return true; - } - return false; + }); } public boolean isUnsafeAccessed() { @@ -379,10 +382,18 @@ public boolean isReachable() { } @Override - public void onReachable() { + public void onReachable(Object reason) { + if (enableTrackAcrossLayers) { + AtomicUtils.atomicSet(this, reason, trackAcrossLayersUpdater); + } notifyReachabilityCallbacks(declaringClass.getUniverse(), new ArrayList<>()); } + @Override + public boolean isTrackedAcrossLayers() { + return AtomicUtils.isSet(this, trackAcrossLayersUpdater); + } + public Object getFieldValueInterceptor() { return fieldValueInterceptor; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java index bc0a65e751f6..dc1b43c5bad2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java @@ -112,6 +112,9 @@ public abstract class AnalysisMethod extends AnalysisElement implements WrappedJ static final AtomicReferenceFieldUpdater allImplementationsUpdater = AtomicReferenceFieldUpdater .newUpdater(AnalysisMethod.class, Object.class, "allImplementations"); + private static final AtomicReferenceFieldUpdater trackAcrossLayersUpdater = AtomicReferenceFieldUpdater + .newUpdater(AnalysisMethod.class, Object.class, "trackAcrossLayers"); + public record Signature(String name, AnalysisType[] parameterTypes) { } @@ -181,6 +184,12 @@ public record Signature(String name, AnalysisType[] parameterTypes) { */ @SuppressWarnings("unused") private volatile Object allImplementations; + /** + * See {@link AnalysisElement#isTrackedAcrossLayers} for explanation. + */ + @SuppressWarnings("unused") private volatile Object trackAcrossLayers; + private final boolean enableTrackAcrossLayers; + /** * Indicates that this method has opaque return. This is necessary when there are control flows * present which cannot be tracked by analysis, which happens for continuation support. @@ -265,6 +274,8 @@ protected AnalysisMethod(AnalysisUniverse universe, ResolvedJavaMethod wrapped, startTrackInvocations(); } parsingContextMaxDepth = PointstoOptions.ParsingContextMaxDepth.getValue(declaringClass.universe.hostVM.options()); + + this.enableTrackAcrossLayers = universe.hostVM.enableTrackAcrossLayers(); } @SuppressWarnings("this-escape") @@ -292,6 +303,8 @@ protected AnalysisMethod(AnalysisMethod original, MultiMethodKey multiMethodKey) if (PointstoOptions.TrackAccessChain.getValue(declaringClass.universe.hostVM().options())) { startTrackInvocations(); } + + this.enableTrackAcrossLayers = original.enableTrackAcrossLayers; } private static String createName(ResolvedJavaMethod wrapped, MultiMethodKey multiMethodKey) { @@ -464,7 +477,7 @@ public boolean analyzedInPriorLayer() { */ public void registerAsIntrinsicMethod(Object reason) { assert isValidReason(reason) : "Registering a method as intrinsic needs to provide a valid reason, found: " + reason; - AtomicUtils.atomicSetAndRun(this, reason, isIntrinsicMethodUpdater, this::onImplementationInvoked); + AtomicUtils.atomicSetAndRun(this, reason, isIntrinsicMethodUpdater, () -> onImplementationInvoked(reason)); } public void registerAsEntryPoint(Object newEntryPointData) { @@ -495,12 +508,12 @@ public boolean registerAsImplementationInvoked(Object reason) { * return before the class gets marked as reachable. */ getDeclaringClass().registerAsReachable("declared method " + qualifiedName + " is registered as implementation invoked"); - return AtomicUtils.atomicSetAndRun(this, reason, isImplementationInvokedUpdater, this::onImplementationInvoked); + return AtomicUtils.atomicSetAndRun(this, reason, isImplementationInvokedUpdater, () -> onImplementationInvoked(reason)); } public void registerAsInlined(Object reason) { assert reason instanceof NodeSourcePosition || reason instanceof ResolvedJavaMethod : "Registering a method as inlined needs to provide the inline location as reason, found: " + reason; - AtomicUtils.atomicSetAndRun(this, reason, isInlinedUpdater, this::onReachable); + AtomicUtils.atomicSetAndRun(this, reason, isInlinedUpdater, () -> onReachable(reason)); } public void registerImplementationInvokedCallback(Consumer callback) { @@ -636,6 +649,11 @@ public boolean isReachable() { return isImplementationInvoked() || isInlined(); } + @Override + public boolean isTrackedAcrossLayers() { + return AtomicUtils.isSet(this, trackAcrossLayersUpdater); + } + @Override public boolean isTriggered() { if (isReachable()) { @@ -644,13 +662,16 @@ public boolean isTriggered() { return isClassInitializer() && getDeclaringClass().isInitialized(); } - public void onImplementationInvoked() { - onReachable(); + public void onImplementationInvoked(Object reason) { + onReachable(reason); notifyImplementationInvokedCallbacks(); } @Override - public void onReachable() { + public void onReachable(Object reason) { + if (enableTrackAcrossLayers) { + AtomicUtils.atomicSet(this, reason, trackAcrossLayersUpdater); + } notifyReachabilityCallbacks(declaringClass.getUniverse(), new ArrayList<>()); } 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 b0b792c0036b..556fb285fd88 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 @@ -108,6 +108,9 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav static final AtomicReferenceFieldUpdater overrideableMethodsUpdater = AtomicReferenceFieldUpdater .newUpdater(AnalysisType.class, Object.class, "overrideableMethods"); + private static final AtomicReferenceFieldUpdater trackAcrossLayersUpdater = AtomicReferenceFieldUpdater + .newUpdater(AnalysisType.class, Object.class, "trackAcrossLayers"); + protected final AnalysisUniverse universe; private final ResolvedJavaType wrapped; private final String qualifiedName; @@ -218,6 +221,12 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav */ @SuppressWarnings("unused") private volatile Object overrideableMethods; + /** + * See {@link AnalysisElement#isTrackedAcrossLayers} for explanation. + */ + @SuppressWarnings("unused") private volatile Object trackAcrossLayers; + private final boolean enableTrackAcrossLayers; + @SuppressWarnings("this-escape") public AnalysisType(AnalysisUniverse universe, ResolvedJavaType javaType, JavaKind storageKind, AnalysisType objectType, AnalysisType cloneableType) { this.universe = universe; @@ -343,6 +352,8 @@ public AnalysisType(AnalysisUniverse universe, ResolvedJavaType javaType, JavaKi AnalysisError.guarantee(universe.getHeapScanner() != null, "Heap scanner is not available."); return universe.getHeapScanner().computeTypeData(this); }); + + this.enableTrackAcrossLayers = universe.hostVM.enableTrackAcrossLayers(); } private AnalysisType[] convertTypes(ResolvedJavaType[] originalTypes) { @@ -592,14 +603,18 @@ public boolean registerAsReachable(Object reason) { * that the onReachable hook for all supertypes is already finished, because they can * still be running in another thread. */ - AtomicUtils.atomicSetAndRun(this, reason, isReachableUpdater, this::onReachable); + AtomicUtils.atomicSetAndRun(this, reason, isReachableUpdater, () -> onReachable(reason)); return true; } return false; } @Override - protected void onReachable() { + protected void onReachable(Object reason) { + if (enableTrackAcrossLayers) { + AtomicUtils.atomicSet(this, reason, trackAcrossLayersUpdater); + } + List> futures = new ArrayList<>(); notifyReachabilityCallbacks(universe, futures); forAllSuperTypes(type -> ConcurrentLightHashSet.forEach(type, subtypeReachableNotificationsUpdater, @@ -860,6 +875,11 @@ public Object getReachableReason() { return isReachable; } + @Override + public boolean isTrackedAcrossLayers() { + return AtomicUtils.isSet(this, trackAcrossLayersUpdater); + } + /** * The kind of the field in memory (in contrast to {@link #getJavaKind()}, which is the kind of * the field on the Java type system level). For example {@link WordBase word types} have a 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 3dade2e9dcf8..984c01ba8f05 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 @@ -176,15 +176,15 @@ public int loadTypeID(Collection types) { return maxTypeID; } - private static boolean isTypeReachable(HostedType type) { - var result = type.getWrapped().isReachable(); - assert type.getTypeID() != -1 : type; - return result; - } - public void persistTypeInfo(Collection types) { for (HostedType type : types) { - if (isTypeReachable(type)) { + /* + * Currently we are calculating type id information for all types. However, for + * types not tracked across layers, the type ID may not be the same in different + * layers. + */ + assert type.getTypeID() != -1 : type; + if (type.getWrapped().isTrackedAcrossLayers()) { int identifierID = type.getWrapped().getId(); int typeID = type.getTypeID(); int numClassTypes = type.getNumClassTypes(); 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 39f61952e6e0..9fd72482d7b9 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 @@ -200,6 +200,7 @@ public enum UsageKind { private final Boolean optionAllowUnsafeAllocationOfAllInstantiatedTypes = SubstrateOptions.AllowUnsafeAllocationOfAllInstantiatedTypes.getValue(); private final boolean isClosedTypeWorld = SubstrateOptions.useClosedTypeWorld(); + private final boolean enableTrackAcrossLayers; @SuppressWarnings("this-escape") public SVMHost(OptionValues options, ImageClassLoader loader, ClassInitializationSupport classInitializationSupport, AnnotationSubstitutionProcessor annotationSubstitutions, @@ -232,6 +233,8 @@ public SVMHost(OptionValues options, ImageClassLoader loader, ClassInitializatio if (SubstrateOptions.includeAll()) { initializeExcludedFields(); } + + enableTrackAcrossLayers = ImageLayerBuildingSupport.buildingSharedLayer(); } @Override @@ -938,6 +941,11 @@ public boolean isClosedTypeWorld() { return isClosedTypeWorld; } + @Override + public boolean enableTrackAcrossLayers() { + return enableTrackAcrossLayers; + } + private final List> neverInlineTrivialHandlers = new CopyOnWriteArrayList<>(); public void registerNeverInlineTrivialHandler(BiPredicate handler) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java index db8cc6c8bac2..b5da8c77a6d6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerWriter.java @@ -258,10 +258,12 @@ private static int getRelocatableConstantMethodId(MethodPointer methodPointer) { } private static int getMethodId(AnalysisMethod analysisMethod) { - if (!analysisMethod.isReachable()) { + if (!analysisMethod.isTrackedAcrossLayers()) { /* - * At the moment, only reachable methods are persisted, so the method will not be loaded - * in the extension image. + * Only tracked methods are persisted, so the method will not be loaded in the extension + * image. + * + * GR-59009 will ensure all methods referred to are tracked. */ return -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 8fa46279ced0..9fbb3ab05491 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 @@ -66,6 +66,10 @@ public static void buildTables(HostedUniverse hUniverse, HostedMetaAccess hMetaA } } + private static boolean shouldIncludeType(HostedType type) { + return type.getWrapped().isReachable() || type.getWrapped().isTrackedAcrossLayers(); + } + private boolean verifyOpenTypeWorldDispatchTables() { HostedMethod invalidVTableEntryHandler = hMetaAccess.lookupJavaMethod(InvalidMethodPointerHandler.INVALID_VTABLE_ENTRY_HANDLER_METHOD); for (HostedType type : hUniverse.getTypes()) { @@ -211,7 +215,7 @@ private void generateOpenTypeWorldDispatchTable(HostedInstanceClass type, Map