From 1a8bb87c0646664720711770666c05f99f1b0b33 Mon Sep 17 00:00:00 2001 From: Codrut Stancu Date: Wed, 14 May 2025 00:27:17 +0200 Subject: [PATCH 1/3] Delay reachability handlers callback execution until analysis is started. --- .../graal/pointsto/heap/ImageHeapScanner.java | 56 ++++++++++--------- .../graal/pointsto/meta/AnalysisUniverse.java | 3 + .../oracle/svm/core/BuildPhaseProvider.java | 9 +++ .../svm/hosted/NativeImageGenerator.java | 2 + .../analysis/NativeImagePointsToAnalysis.java | 2 +- .../svm/hosted/heap/SVMImageHeapScanner.java | 15 +++++ 6 files changed, 60 insertions(+), 27 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java index 1e86b9485daa..c95caee61352 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java @@ -130,33 +130,36 @@ public void scanEmbeddedRoot(JavaConstant root, BytecodePosition position) { public void onFieldRead(AnalysisField field) { assert field.isRead() : field; /* Check if the value is available before accessing it. */ - AnalysisType declaringClass = field.getDeclaringClass(); if (field.isStatic()) { - FieldScan reason = new FieldScan(field); - if (!field.installableInLayer()) { - /* - * For non-installable static fields we do not scan the constant value, but instead - * inject its type state in the field flow. This will be propagated to any - * corresponding field loads. - * - * GR-52421: the field state needs to be serialized from the base layer analysis - */ - if (field.getStorageKind().isObject()) { - bb.injectFieldTypes(field, List.of(field.getType()), true); - } else if (bb.trackPrimitiveValues() && field.getStorageKind().isPrimitive()) { - ((PointsToAnalysisField) field).saturatePrimitiveField(); - } - } else if (isValueAvailable(field)) { - JavaConstant fieldValue = readStaticFieldValue(field); - if (fieldValue instanceof ImageHeapConstant imageHeapConstant && field.isFinal()) { - AnalysisError.guarantee(imageHeapConstant.getOrigin() != null, "The origin of the constant %s should have been registered before", imageHeapConstant); - } - markReachable(fieldValue, reason); - notifyAnalysis(field, null, fieldValue, reason); - } + postTask(() -> onStaticFieldRead(field)); } else { /* Trigger field scanning for the already processed objects. */ - postTask(() -> onInstanceFieldRead(field, declaringClass)); + postTask(() -> onInstanceFieldRead(field, field.getDeclaringClass())); + } + } + + private void onStaticFieldRead(AnalysisField field) { + FieldScan reason = new FieldScan(field); + if (!field.installableInLayer()) { + /* + * For non-installable static fields we do not scan the constant value, but instead + * inject its type state in the field flow. This will be propagated to any corresponding + * field loads. + * + * GR-52421: the field state needs to be serialized from the base layer analysis + */ + if (field.getStorageKind().isObject()) { + bb.injectFieldTypes(field, List.of(field.getType()), true); + } else if (bb.trackPrimitiveValues() && field.getStorageKind().isPrimitive()) { + ((PointsToAnalysisField) field).saturatePrimitiveField(); + } + } else if (isValueAvailable(field)) { + JavaConstant fieldValue = readStaticFieldValue(field); + if (fieldValue instanceof ImageHeapConstant imageHeapConstant && field.isFinal()) { + AnalysisError.guarantee(imageHeapConstant.getOrigin() != null, "The origin of the constant %s should have been registered before", imageHeapConstant); + } + markReachable(fieldValue, reason); + notifyAnalysis(field, null, fieldValue, reason); } } @@ -890,9 +893,10 @@ protected AnalysisField lookupJavaField(String className, String fieldName) { * * In the (legacy) Feature.duringAnalysis state, the executor is not running and we must not * schedule new tasks, because that would be treated as "the analysis has not finished yet". So - * in that case we execute the task directly. + * in that case we execute the task directly. A task that runs in the Feature.duringAnalysis + * stage and modifies the analysis state should itself trigger an additional analysis iteration. */ - private void maybeRunInExecutor(CompletionExecutor.DebugContextRunnable task) { + protected void maybeRunInExecutor(CompletionExecutor.DebugContextRunnable task) { if (bb.executorIsStarted()) { bb.postTask(task); } else { 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 cf08d82dacca..76699b2bfcf0 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 @@ -787,6 +787,9 @@ public void setConcurrentAnalysisAccess(DuringAnalysisAccess access) { } public DuringAnalysisAccess getConcurrentAnalysisAccess() { + AnalysisError.guarantee(concurrentAnalysisAccess != null, "The requested DuringAnalysisAccess object is not available. " + + "This means that an analysis task is executed too eagerly, before analysis. " + + "Make sure that all analysis tasks are posted to the analysis execution engine."); return concurrentAnalysisAccess; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java index d9d9b4a431fe..81471f8ded34 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildPhaseProvider.java @@ -35,6 +35,7 @@ public final class BuildPhaseProvider { private boolean featureRegistrationFinished; private boolean setupFinished; + private boolean analysisStarted; private boolean analysisFinished; private boolean hostedUniverseBuilt; private boolean readyForCompilation; @@ -69,6 +70,14 @@ public static boolean isSetupFinished() { return ImageSingletons.contains(BuildPhaseProvider.class) && singleton().setupFinished; } + public static void markAnalysisStarted() { + singleton().analysisStarted = true; + } + + public static boolean isAnalysisStarted() { + return ImageSingletons.contains(BuildPhaseProvider.class) && singleton().analysisStarted; + } + public static void markAnalysisFinished() { singleton().analysisFinished = true; } 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 5f6e5f986b3e..c17f7ce42d36 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 @@ -838,6 +838,8 @@ protected boolean runPointsToAnalysis(String imageName, OptionValues options, De */ HostedImageLayerBuildingSupport.singleton().getLoader().relinkTransformedStaticFinalFieldValues(); } + /* All pre-analysis set-up is done and the fixed-point analysis can start. */ + BuildPhaseProvider.markAnalysisStarted(); bb.runAnalysis(debug, (universe) -> { try (StopTimer t2 = TimerCollection.createTimerAndStart(TimerCollection.Registry.FEATURES)) { bb.getHostVM().notifyClassReachabilityListener(universe, config); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java index 175c1934c283..c08ebd6e0b04 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java @@ -128,7 +128,7 @@ public AnnotationSubstitutionProcessor getAnnotationSubstitutionProcessor() { @Override public void onFieldAccessed(AnalysisField field) { - customTypeFieldHandler.handleField(field); + postTask(() -> customTypeFieldHandler.handleField(field)); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java index 23a57d592d45..f752425f1216 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java @@ -40,6 +40,8 @@ import com.oracle.graal.pointsto.heap.ImageHeapScanner; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.util.CompletionExecutor; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; @@ -132,4 +134,17 @@ protected void onObjectReachable(ImageHeapConstant imageHeapConstant, ScanReason reflectionSupport.registerHeapDynamicHub(hub, reason); } } + + @Override + protected void maybeRunInExecutor(CompletionExecutor.DebugContextRunnable task) { + if (BuildPhaseProvider.isAnalysisStarted()) { + super.maybeRunInExecutor(task); + } else { + /* + * Before the analysis is started post all scanning tasks to the executor. They will be + * executed after the analysis starts. + */ + bb.postTask(task); + } + } } From 9b96761cc8180ad1182c20dc1e667133273d091f Mon Sep 17 00:00:00 2001 From: Codrut Stancu Date: Wed, 14 May 2025 12:23:45 +0200 Subject: [PATCH 2/3] Make ConcurrentAnalysisAccessImpl available before-analysis. --- .../com/oracle/graal/pointsto/heap/ImageHeapScanner.java | 2 +- .../com/oracle/graal/pointsto/meta/AnalysisField.java | 6 +++--- .../src/com/oracle/graal/pointsto/meta/AnalysisType.java | 5 +++-- .../src/com/oracle/svm/hosted/NativeImageGenerator.java | 9 +++++++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java index c95caee61352..2b5e226b7d00 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java @@ -608,7 +608,7 @@ protected void onObjectReachable(ImageHeapConstant imageHeapConstant, ScanReason * conditions, e.g., a started Thread should never be added to the image heap, but * the structure of the object is valid, as ensured by the validity check above. */ - objectType.notifyObjectReachable(universe.getConcurrentAnalysisAccess(), object, reason); + objectType.notifyObjectReachable(object, reason); } catch (UnsupportedFeatureException e) { /* Enhance the unsupported feature message with the object trace and rethrow. */ StringBuilder backtrace = new StringBuilder(); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java index 08972a68cd0a..3bebe6e067d1 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java @@ -517,9 +517,9 @@ public JavaConstant getConstantValue() { } /** - * Ensure that all reachability handler that were present at the time the declaring type was - * marked as reachable are executed before accessing field values. This allows field value - * transformer to be installed reliably in reachability handler. + * Ensure that all reachability handlers that were present at the time the declaring type was + * marked as reachable are executed before accessing field values. This allows a field value + * transformer to be installed reliably in a reachability handler. */ public void beforeFieldValueAccess() { declaringClass.registerAsReachable(this); 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 ad8ea80cd485..baea022b094e 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 @@ -700,8 +700,9 @@ public void registerObjectReachableCallback(ObjectReachableCallback callb }); } - public void notifyObjectReachable(DuringAnalysisAccess access, T object, ScanReason reason) { - ConcurrentLightHashSet.forEach(this, objectReachableCallbacksUpdater, (ObjectReachableCallback c) -> c.doCallback(access, object, reason)); + public void notifyObjectReachable(T object, ScanReason reason) { + ConcurrentLightHashSet.forEach(this, objectReachableCallbacksUpdater, + (ObjectReachableCallback c) -> c.doCallback(universe.getConcurrentAnalysisAccess(), object, reason)); } public void registerInstantiatedCallback(Consumer callback) { 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 c17f7ce42d36..a001f9d4c358 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 @@ -811,6 +811,13 @@ protected void createAbstractImage(NativeImageKind k, List hostedE @SuppressWarnings("try") protected boolean runPointsToAnalysis(String imageName, OptionValues options, DebugContext debug) { try (Indent ignored = debug.logAndIndent("run analysis")) { + /* + * Set the ConcurrentAnalysisAccessImpl before Feature.beforeAnalysis is executed. Some + * features may already execute some pre-analysis tasks, e.g., reading a hosted field + * value, that can trigger reachability callbacks. + */ + ConcurrentAnalysisAccessImpl concurrentConfig = new ConcurrentAnalysisAccessImpl(featureHandler, loader, bb, nativeLibraries, debug); + aUniverse.setConcurrentAnalysisAccess(concurrentConfig); try (Indent ignored1 = debug.logAndIndent("process analysis initializers")) { BeforeAnalysisAccessImpl config = new BeforeAnalysisAccessImpl(featureHandler, loader, bb, nativeLibraries, debug); ServiceCatalogSupport.singleton().enableServiceCatalogMapTransformer(config); @@ -828,8 +835,6 @@ protected boolean runPointsToAnalysis(String imageName, OptionValues options, De try (ReporterClosable c = ProgressReporter.singleton().printAnalysis(bb.getUniverse(), nativeLibraries.getLibraries())) { DuringAnalysisAccessImpl config = new DuringAnalysisAccessImpl(featureHandler, loader, bb, nativeLibraries, debug); try { - ConcurrentAnalysisAccessImpl concurrentConfig = new ConcurrentAnalysisAccessImpl(featureHandler, loader, bb, nativeLibraries, debug); - aUniverse.setConcurrentAnalysisAccess(concurrentConfig); if (ImageLayerBuildingSupport.buildingExtensionLayer()) { /* * All the field value transformers should be installed by this point. From cc77fb8f3f2015308d2bccd4ab2574f7a56b76cf Mon Sep 17 00:00:00 2001 From: Codrut Stancu Date: Wed, 14 May 2025 01:06:00 +0200 Subject: [PATCH 3/3] Add ReachabilityHandlerAssertFeature. --- .../ReachabilityHandlerAssertFeature.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandlerAssertFeature.java diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandlerAssertFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandlerAssertFeature.java new file mode 100644 index 000000000000..892da6a2b5d5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandlerAssertFeature.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted; + +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.util.VMError; + +/** + * Ensure that a {@link org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess} object is + * available when a reachability callback is executed for any of the reachable types. + */ +@AutomaticallyRegisteredFeature +public class ReachabilityHandlerAssertFeature implements InternalFeature { + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return SubstrateUtil.assertionsEnabled(); + } + + @Override + public void beforeAnalysis(Feature.BeforeAnalysisAccess access) { + access.registerSubtypeReachabilityHandler(this::onTypeReachable, Object.class); + } + + private void onTypeReachable(DuringAnalysisAccess access, Class cls) { + VMError.guarantee(access != null, "DuringAnalysisAccess object is not available when processing class %s.", cls.getName()); + } +}