From 6f3731c3af37794e826a3310ec95669f3b717a99 Mon Sep 17 00:00:00 2001 From: Codrut Stancu Date: Fri, 8 Apr 2022 10:11:07 -0700 Subject: [PATCH 1/2] Run reachability handlers concurrently during analysis. --- .../graalvm/nativeimage/hosted/Feature.java | 25 ++- .../graal/pointsto/meta/AnalysisElement.java | 137 ++++++++++++++++ .../graal/pointsto/meta/AnalysisField.java | 20 ++- .../graal/pointsto/meta/AnalysisMethod.java | 97 ++++++++++-- .../graal/pointsto/meta/AnalysisType.java | 148 ++++++++++++++--- .../graal/pointsto/meta/AnalysisUniverse.java | 74 ++++----- .../graal/pointsto/util/AtomicUtils.java | 19 +++ .../com/oracle/svm/core/SubstrateOptions.java | 3 + .../hosted/ConcurrentReachabilityHandler.java | 149 ++++++++++++++++++ .../com/oracle/svm/hosted/FeatureImpl.java | 43 ++++- .../svm/hosted/NativeImageGenerator.java | 3 + .../svm/hosted/ProtectionDomainFeature.java | 4 +- .../svm/hosted/ReachabilityHandler.java | 44 ++++++ .../hosted/ReachabilityHandlerFeature.java | 18 ++- .../com/oracle/svm/hosted/jfr/JfrFeature.java | 4 +- .../hosted/xml/XMLParsersRegistration.java | 9 +- .../methodhandles/MethodHandleFeature.java | 4 +- 17 files changed, 700 insertions(+), 101 deletions(-) create mode 100644 substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisElement.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConcurrentReachabilityHandler.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandler.java diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java index e8a3492499e8..80771396918c 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -232,11 +232,24 @@ interface BeforeAnalysisAccess extends FeatureAccess { void registerReachabilityHandler(Consumer callback, Object... elements); /** - * Registers a callback that is invoked once {@link Feature#duringAnalysis during analysis} - * for each time a method that overrides the specified {param baseMethod} is determined to - * be reachable at run time. In addition the handler will also get invoked once when the - * {param baseMethod} itself becomes reachable. The specific method that becomes reachable - * is passed to the handler as the second parameter. + * Registers a callback that is invoked once during analysis for each time a method that + * overrides the specified {param baseMethod} is determined to be reachable at run time. In + * addition, the handler will also get invoked once when the {param baseMethod} itself + * becomes reachable. The specific method that becomes reachable is passed to the handler as + * the second parameter. + *

+ * A method is considered reachable at run time if it can be executed, as determined via + * static analysis. + *

+ * Therefore, if a method can be statically bound (usually, that means it is final or + * private or static, but not abstract, or the declaring class is final), or it is a + * constructors, and it is the target of a reachable invoke, or it is inlined, then it is + * considered run time reachable. + *

+ * A virtual methods is considered run time reachable if its declaring-class or any of its + * subtypes is instantiated and the method is the target of a reachable invoke, or it is + * inlined. Even if the declaring type itself is not marked as instantiated the method can + * still be reachable via special invokes, e.g., `super` calls. * * @since 19.3 */ 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 new file mode 100644 index 000000000000..4f8765c68766 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisElement.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022, 2022, 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.graal.pointsto.meta; + +import java.lang.reflect.Executable; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; + +import com.oracle.graal.pointsto.PointsToAnalysis; + +public abstract class AnalysisElement { + + /** + * Contains reachability handlers that are notified when the element is marked as reachable. + * Each handler is notified only once, and then it is removed from the set. + */ + private final Set elementReachableNotifications = ConcurrentHashMap.newKeySet(); + + public void registerReachabilityNotification(ElementReachableNotification notification) { + elementReachableNotifications.add(notification); + } + + public void notifyReachabilityCallback(AnalysisUniverse universe, ElementReachableNotification notification) { + notification.notifyCallback(universe, this); + elementReachableNotifications.remove(notification); + } + + protected void notifyReachabilityCallbacks(AnalysisUniverse universe) { + elementReachableNotifications.forEach(c -> c.notifyCallback(universe, this)); + elementReachableNotifications.removeIf(ElementReachableNotification::isNotified); + } + + public abstract boolean isReachable(); + + protected abstract void onReachable(); + + /** Return true if reachability handlers should be executed for this element. */ + public boolean isTriggered() { + return isReachable(); + } + + public static final class ElementReachableNotification { + + private final Consumer callback; + private final AtomicBoolean notified = new AtomicBoolean(); + + public ElementReachableNotification(Consumer callback) { + this.callback = callback; + } + + public boolean isNotified() { + return notified.get(); + } + + /** + * Notify the callback exactly once. Note that this callback can be shared by multiple + * triggers, the one that triggers it is passed into triggeredElement for debugging. + */ + private void notifyCallback(AnalysisUniverse universe, AnalysisElement triggeredElement) { + assert triggeredElement.isTriggered(); + if (!notified.getAndSet(true)) { + execute(universe, () -> callback.accept(universe.getConcurrentAnalysisAccess())); + } + } + } + + public static final class SubtypeReachableNotification { + private final BiConsumer> callback; + private final Set seenSubtypes = ConcurrentHashMap.newKeySet(); + + public SubtypeReachableNotification(BiConsumer> callback) { + this.callback = callback; + } + + /** Notify the callback exactly once for each reachable subtype. */ + public void notifyCallback(AnalysisUniverse universe, AnalysisType reachableSubtype) { + assert reachableSubtype.isReachable(); + if (seenSubtypes.add(reachableSubtype)) { + execute(universe, () -> callback.accept(universe.getConcurrentAnalysisAccess(), reachableSubtype.getJavaClass())); + } + } + } + + public static final class MethodOverrideReachableNotification { + private final BiConsumer callback; + private final Set seenOverride = ConcurrentHashMap.newKeySet(); + + public MethodOverrideReachableNotification(BiConsumer callback) { + this.callback = callback; + } + + /** Notify the callback exactly once for each reachable method override. */ + public void notifyCallback(AnalysisUniverse universe, AnalysisMethod reachableOverride) { + assert reachableOverride.isReachable(); + if (seenOverride.add(reachableOverride)) { + execute(universe, () -> callback.accept(universe.getConcurrentAnalysisAccess(), reachableOverride.getJavaMethod())); + } + } + } + + private static void execute(AnalysisUniverse universe, Runnable task) { + /* + * Post the tasks to the analysis executor. This ensures that even for elements registered + * as reachable early, before the analysis is started, the reachability callbacks are run + * during the analysis. + */ + ((PointsToAnalysis) universe.getBigbang()).postTask(task); + } + +} 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 1dfe3f213351..890576899f4f 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 @@ -37,8 +37,8 @@ import org.graalvm.util.GuardedAnnotationAccess; import com.oracle.graal.pointsto.api.DefaultUnsafePartition; -import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.api.HostVM; +import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.flow.ContextInsensitiveFieldTypeFlow; import com.oracle.graal.pointsto.flow.FieldTypeFlow; import com.oracle.graal.pointsto.flow.MethodTypeFlow; @@ -52,7 +52,7 @@ import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaType; -public abstract class AnalysisField implements ResolvedJavaField, OriginalFieldProvider { +public abstract class AnalysisField extends AnalysisElement implements ResolvedJavaField, OriginalFieldProvider { @SuppressWarnings("rawtypes")// private static final AtomicReferenceFieldUpdater OBSERVERS_UPDATER = // @@ -242,6 +242,7 @@ public boolean registerAsAccessed() { boolean firstAttempt = AtomicUtils.atomicMark(isAccessed); notifyUpdateAccessInfo(); if (firstAttempt) { + onReachable(); getUniverse().onFieldAccessed(this); getUniverse().getHeapScanner().onFieldRead(this); } @@ -255,6 +256,7 @@ public boolean registerAsRead(MethodTypeFlow method) { readBy.put(method, Boolean.TRUE); } if (firstAttempt) { + onReachable(); getUniverse().onFieldAccessed(this); getUniverse().getHeapScanner().onFieldRead(this); } @@ -273,8 +275,11 @@ public boolean registerAsWritten(MethodTypeFlow method) { if (writtenBy != null && method != null) { writtenBy.put(method, Boolean.TRUE); } - if (firstAttempt && (Modifier.isVolatile(getModifiers()) || getStorageKind() == JavaKind.Object)) { - getUniverse().onFieldAccessed(this); + if (firstAttempt) { + onReachable(); + if (Modifier.isVolatile(getModifiers()) || getStorageKind() == JavaKind.Object) { + getUniverse().onFieldAccessed(this); + } } return firstAttempt; } @@ -282,6 +287,7 @@ public boolean registerAsWritten(MethodTypeFlow method) { public void markFolded() { if (AtomicUtils.atomicMark(isFolded)) { getDeclaringClass().registerAsReachable(); + onReachable(); } } @@ -385,10 +391,16 @@ public boolean isFolded() { return isFolded.get(); } + @Override public boolean isReachable() { return isAccessed.get() || isRead.get() || isWritten.get() || isFolded.get(); } + @Override + public void onReachable() { + notifyReachabilityCallbacks(declaringClass.getUniverse()); + } + public void setCanBeNull(boolean canBeNull) { this.canBeNull = canBeNull; notifyUpdateAccessInfo(); 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 21e9cbbb427b..5cad9ed4c1bb 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 @@ -70,7 +70,7 @@ import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.SpeculationLog; -public abstract class AnalysisMethod implements WrappedJavaMethod, GraphProvider, OriginalMethodProvider { +public abstract class AnalysisMethod extends AnalysisElement implements WrappedJavaMethod, GraphProvider, OriginalMethodProvider { public final ResolvedJavaMethod wrapped; @@ -86,11 +86,11 @@ public abstract class AnalysisMethod implements WrappedJavaMethod, GraphProvider private final AtomicBoolean isVirtualRootMethod = new AtomicBoolean(); /** Direct (special or static) invoked method registered as root. */ private final AtomicBoolean isDirectRootMethod = new AtomicBoolean(); - private boolean isIntrinsicMethod; private Object entryPointData; private final AtomicBoolean isInvoked = new AtomicBoolean(); private final AtomicBoolean isImplementationInvoked = new AtomicBoolean(); - private boolean isInlined; + private final AtomicBoolean isIntrinsicMethod = new AtomicBoolean(); + private final AtomicBoolean isInlined = new AtomicBoolean(); private final AtomicReference parsedGraphCacheState = new AtomicReference<>(GRAPH_CACHE_UNPARSED); private static final Object GRAPH_CACHE_UNPARSED = "unparsed"; @@ -202,7 +202,7 @@ public int getId() { * resolution must be able to find the method (otherwise the intrinsification would not work). */ public void registerAsIntrinsicMethod() { - isIntrinsicMethod = true; + AtomicUtils.atomicMarkAndRun(isIntrinsicMethod, this::onReachable); } public void registerAsEntryPoint(Object newEntryPointData) { @@ -231,11 +231,11 @@ public boolean registerAsImplementationInvoked() { * return before the class gets marked as reachable. */ getDeclaringClass().registerAsReachable(); - return AtomicUtils.atomicMark(isImplementationInvoked); + return AtomicUtils.atomicMarkAndRun(isImplementationInvoked, this::onReachable); } public void registerAsInlined() { - isInlined = true; + AtomicUtils.atomicMarkAndRun(isInlined, this::onReachable); } /** Get the set of all callers for this method, as inferred by the static analysis. */ @@ -255,7 +255,7 @@ public Object getEntryPointData() { } public boolean isIntrinsicMethod() { - return isIntrinsicMethod; + return isIntrinsicMethod.get(); } /** @@ -311,7 +311,7 @@ public boolean isSimplyImplementationInvoked() { * Returns true if this method is ever used as the target of a call site. */ public boolean isInvoked() { - return isIntrinsicMethod || isVirtualRootMethod() || isDirectRootMethod() || isInvoked.get(); + return isIntrinsicMethod.get() || isVirtualRootMethod() || isDirectRootMethod() || isInvoked.get(); } /** @@ -319,11 +319,88 @@ public boolean isInvoked() { * registered as implementation invoked when they are linked. */ public boolean isImplementationInvoked() { - return !Modifier.isAbstract(getModifiers()) && (isIntrinsicMethod || isImplementationInvoked.get()); + return !Modifier.isAbstract(getModifiers()) && (isIntrinsicMethod.get() || isImplementationInvoked.get()); } + @Override public boolean isReachable() { - return isImplementationInvoked() || isInlined; + return isImplementationInvoked() || isInlined.get(); + } + + @Override + public boolean isTriggered() { + if (isReachable()) { + return true; + } + return isClassInitializer() && getDeclaringClass().isInitialized(); + } + + @Override + public void onReachable() { + notifyReachabilityCallbacks(declaringClass.getUniverse()); + processMethodOverrides(); + } + + private void processMethodOverrides() { + if (wrapped.canBeStaticallyBound() || isConstructor()) { + notifyMethodOverride(this); + } else if (declaringClass.isAnySubtypeInstantiated()) { + /* + * If neither the declaring class nor a subtype is instantiated then this method cannot + * be marked as invoked, so it cannot be an override. + */ + declaringClass.forAllSuperTypes(superType -> { + /* + * Iterate all the super types (including this type itself) looking for installed + * override notifications. If this method resolves in a super type, and it has an + * override handler installed in that type, pass this method to the callback. It + * doesn't matter if the superMethod is actually reachable, only if it has any + * override handlers installed. + */ + AnalysisMethod superMethod = resolveInType(superType); + if (superMethod != null) { + superMethod.notifyMethodOverride(AnalysisMethod.this); + } + }); + } + } + + protected void notifyMethodOverride(AnalysisMethod override) { + declaringClass.getOverrideReachabilityNotifications(this).forEach(n -> n.notifyCallback(getUniverse(), override)); + } + + public void registerOverrideReachabilityNotification(MethodOverrideReachableNotification notification) { + declaringClass.registerOverrideReachabilityNotification(this, notification); + } + + /** + * Resolves this method in the provided type, but only if the type or any of its subtypes is + * marked as instantiated. + */ + protected AnalysisMethod resolveInType(AnalysisType holder) { + return resolveInType(holder, holder.isAnySubtypeInstantiated()); + } + + protected AnalysisMethod resolveInType(AnalysisType holder, boolean holderOrSubtypeInstantiated) { + /* + * If the holder and all subtypes are not instantiated, then we do not need to resolve the + * method. The method cannot be marked as invoked. + */ + if (holderOrSubtypeInstantiated || isIntrinsicMethod()) { + AnalysisMethod resolved; + try { + resolved = holder.resolveConcreteMethod(this, null); + } catch (UnsupportedFeatureException e) { + /* An unsupported overriding method is not reachable. */ + resolved = null; + } + /* + * resolved == null means that the method in the base class was called, but never with + * this holder. + */ + return resolved; + } + return null; } @Override 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 e804e318282a..9718d2a0b417 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 @@ -29,7 +29,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @@ -72,7 +74,7 @@ import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; -public abstract class AnalysisType implements WrappedJavaType, OriginalClassProvider, Comparable { +public abstract class AnalysisType extends AnalysisElement implements WrappedJavaType, OriginalClassProvider, Comparable { @SuppressWarnings("rawtypes")// private static final AtomicReferenceFieldUpdater UNSAFE_ACCESS_FIELDS_UPDATER = // @@ -91,6 +93,7 @@ public abstract class AnalysisType implements WrappedJavaType, OriginalClassProv private final AtomicBoolean isInHeap = new AtomicBoolean(); private final AtomicBoolean isAllocated = new AtomicBoolean(); private final AtomicBoolean isReachable = new AtomicBoolean(); + private final AtomicBoolean isAnySubtypeInstantiated = new AtomicBoolean(); private boolean reachabilityListenerNotified; private boolean unsafeFieldsRecomputed; private boolean unsafeAccessedFieldsRegistered; @@ -161,6 +164,19 @@ public enum UsageKind { */ private AnalysisFuture typeData; + /** + * Contains reachability handlers that are notified when any of the subtypes are marked as + * reachable. Each handler is notified only once per subtype. + */ + private final Set subtypeReachableNotifications = ConcurrentHashMap.newKeySet(); + + /** + * Contains reachability handlers that are notified when any of the method override becomes + * reachable *and* the declaring class of the override (or any subtype) is instantiated. Each + * handler is notified only once per method override. + */ + private final Map> overrideReachableNotifications = new ConcurrentHashMap<>(); + AnalysisType(AnalysisUniverse universe, ResolvedJavaType javaType, JavaKind storageKind, AnalysisType objectType, AnalysisType cloneableType) { this.universe = universe; this.wrapped = javaType; @@ -420,7 +436,7 @@ public static boolean verifyAssignableTypes(BigBang bb) { public boolean registerAsInHeap() { registerAsReachable(); if (AtomicUtils.atomicMark(isInHeap)) { - universe.onTypeInstantiated(this, UsageKind.InHeap); + onInstantiated(UsageKind.InHeap); return true; } return false; @@ -432,12 +448,47 @@ public boolean registerAsInHeap() { public boolean registerAsAllocated(Node node) { registerAsReachable(); if (AtomicUtils.atomicMark(isAllocated)) { - universe.onTypeInstantiated(this, UsageKind.Allocated); + onInstantiated(UsageKind.Allocated); return true; } return false; } + private void onInstantiated(UsageKind usage) { + universe.onTypeInstantiated(this, usage); + processMethodOverrides(); + } + + private void processMethodOverrides() { + /* + * Walk up the type hierarchy from this type keeping track of all processed types. For each + * superType iterate all the override notifications and resolve the methods in all the seen + * types, which are subtypes from the point of view of the superType itself. Thus, although + * only *this* type may be marked as instantiated, any *intermediate* types between this + * type and a super type that declares override notifications will be processed and any + * overrides, if reachable, will be passed to the callback. These intermediate types, i.e., + * the seenSubtypes set, may not be instantiated but the overrides that they declare, if + * reachable, could be specially invoked, e.g., via super calls. Note that the baseMethod is + * not actually an override of itself, but the `registerMethodOverrideReachabilityHandler` + * API explicitly includes the base method too. + */ + Set seenSubtypes = new HashSet<>(); + forAllSuperTypes(superType -> { + AtomicUtils.atomicMark(superType.isAnySubtypeInstantiated); + seenSubtypes.add(superType); + for (var entry : superType.overrideReachableNotifications.entrySet()) { + AnalysisMethod baseMethod = entry.getKey(); + Set overrideNotifications = entry.getValue(); + for (AnalysisType subType : seenSubtypes) { + AnalysisMethod override = baseMethod.resolveInType(subType); + if (override != null && override.isReachable()) { + overrideNotifications.forEach(n -> n.notifyCallback(universe, override)); + } + } + } + }); + } + /** * Register the type as assignable with all its super types. This is a blocking call to ensure * that the type is registered with all its super types before it is propagated by the analysis @@ -456,27 +507,42 @@ public void registerAsAssignable(BigBang bb) { public boolean registerAsReachable() { if (!isReachable.get()) { - forAllSuperTypes(AnalysisType::markReachable); + /* Mark this type and all its super types as reachable. */ + forAllSuperTypes(type -> AtomicUtils.atomicMarkAndRun(type.isReachable, type::onReachable)); return true; } return false; } - private void markReachable() { - if (AtomicUtils.atomicMark(isReachable)) { - universe.notifyReachableType(); - universe.hostVM.checkForbidden(this, UsageKind.Reachable); - if (isArray()) { - /* - * For array types, distinguishing between "used" and "instantiated" does not - * provide any benefits since array types do not implement new methods. Marking all - * used array types as instantiated too allows more usages of Arrays.newInstance - * without the need of explicit registration of types for reflection. - */ - registerAsAllocated(null); - } - ensureInitialized(); + @Override + protected void onReachable() { + notifyReachabilityCallbacks(universe); + forAllSuperTypes(type -> type.subtypeReachableNotifications.forEach(n -> n.notifyCallback(universe, this))); + universe.notifyReachableType(); + universe.hostVM.checkForbidden(this, UsageKind.Reachable); + if (isArray()) { + /* + * For array types, distinguishing between "used" and "instantiated" does not provide + * any benefits since array types do not implement new methods. Marking all used array + * types as instantiated too allows more usages of Arrays.newInstance without the need + * of explicit registration of types for reflection. + */ + registerAsAllocated(null); } + ensureInitialized(); + } + + public void registerSubtypeReachabilityNotification(SubtypeReachableNotification notification) { + subtypeReachableNotifications.add(notification); + } + + public void registerOverrideReachabilityNotification(AnalysisMethod declaredMethod, MethodOverrideReachableNotification notification) { + assert declaredMethod.getDeclaringClass() == this; + overrideReachableNotifications.computeIfAbsent(declaredMethod, m -> ConcurrentHashMap.newKeySet()).add(notification); + } + + public Set getOverrideReachabilityNotifications(AnalysisMethod method) { + return overrideReachableNotifications.getOrDefault(method, Collections.emptySet()); } /** @@ -540,6 +606,24 @@ public void ensureInitialized() { initializationTask.ensureDone(); } + /** + * Called when the {@link AnalysisType} initialization task is processed. This doesn't mean that + * the underlying {@link Class} is actually initialized, i.e., it may be initialized at run + * time. + */ + public void onInitialized() { + if (isInitialized()) { + /* + * This type is initialized at build time, so the class initializer, if any, cannot be + * reachable at run time. Notify the reachability callbacks for the class initializer. + */ + AnalysisMethod clinit = this.getClassInitializer(); + if (clinit != null) { + clinit.notifyReachabilityCallbacks(universe); + } + } + } + public boolean getReachabilityListenerNotified() { return reachabilityListenerNotified; } @@ -651,6 +735,11 @@ public boolean isInstantiated() { return instantiated; } + /** Returns true if this type or any of its subtypes was marked as instantiated. */ + public boolean isAnySubtypeInstantiated() { + return isAnySubtypeInstantiated.get(); + } + /** * Returns true if all instance fields which hold offsets to unsafe field accesses are already * recomputed with the correct values from the substrate object layout. Which means that those @@ -660,6 +749,7 @@ public boolean unsafeFieldsRecomputed() { return unsafeFieldsRecomputed; } + @Override public boolean isReachable() { return isReachable.get(); } @@ -814,6 +904,28 @@ private void addSubType(AnalysisType subType) { this.subTypes.add(subType); } + /** + * Collects and returns *all* subtypes of this type, not only the immediate subtypes, including + * this type itself, regardless of reachability status. To access the immediate subtypes use + * {@link AnalysisType#getSubTypes()}. + * + * Since the subtypes are updated continuously as the universe is expanded this method may + * return different results on each call, until the analysis universe reaches a stable state. + */ + public Set getAllSubtypes() { + HashSet result = new HashSet<>(); + collectSubtypes(this, result); + return result; + } + + private static void collectSubtypes(AnalysisType baseType, Set result) { + for (AnalysisType subType : baseType.getSubTypes()) { + if (result.add(subType)) { + collectSubtypes(subType, result); + } + } + } + @Override public AnalysisType findLeastCommonAncestor(ResolvedJavaType otherType) { ResolvedJavaType subst = universe.substitutions.resolve(((AnalysisType) otherType).wrapped); 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 0dd759c44b14..e9b1e6169ba3 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 @@ -28,7 +28,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -40,6 +39,7 @@ import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; import org.graalvm.compiler.core.common.SuppressFBWarnings; +import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; import org.graalvm.word.WordBase; import com.oracle.graal.pointsto.AnalysisPolicy; @@ -123,6 +123,7 @@ public class AnalysisUniverse implements Universe { private ImageHeapScanner heapScanner; private HeapSnapshotVerifier heapVerifier; private BigBang bb; + private DuringAnalysisAccess concurrentAnalysisAccess; public JavaKind getWordKind() { return wordKind; @@ -584,26 +585,25 @@ public Object replaceObject(Object source) { return destination; } + public static Set reachableMethodOverrides(AnalysisMethod baseMethod) { + return getMethodImplementations(baseMethod, true); + } + private void collectMethodImplementations() { for (AnalysisMethod method : methods.values()) { - Set implementations = getMethodImplementations(bb, method, false); + Set implementations = getMethodImplementations(method, false); method.implementations = implementations.toArray(new AnalysisMethod[implementations.size()]); } } - public static Set getMethodImplementations(BigBang bb, AnalysisMethod method, boolean includeInlinedMethods) { + private static Set getMethodImplementations(AnalysisMethod method, boolean includeInlinedMethods) { Set implementations = new LinkedHashSet<>(); if (method.wrapped.canBeStaticallyBound() || method.isConstructor()) { if (includeInlinedMethods ? method.isReachable() : method.isImplementationInvoked()) { implementations.add(method); } } else { - try { - collectMethodImplementations(method, method.getDeclaringClass(), implementations, includeInlinedMethods); - } catch (UnsupportedFeatureException ex) { - String message = String.format("Error while collecting implementations of %s : %s%n", method.format("%H.%n(%p)"), ex.getMessage()); - bb.getUnsupportedFeatures().addMessage(method.format("%H.%n(%p)"), method, message, null, ex.getCause()); - } + collectMethodImplementations(method, method.getDeclaringClass(), implementations, includeInlinedMethods); } return implementations; } @@ -618,50 +618,27 @@ private static boolean collectMethodImplementations(AnalysisMethod method, Analy holderOrSubtypeInstantiated |= collectMethodImplementations(method, subClass, implementations, includeInlinedMethods); } - /* - * If the holder and all subtypes are not instantiated, then we do not need to resolve the - * method. The method cannot be marked as invoked. - */ - if (holderOrSubtypeInstantiated || method.isIntrinsicMethod()) { - AnalysisMethod aResolved; - try { - aResolved = holder.resolveConcreteMethod(method, null); - } catch (UnsupportedFeatureException e) { - /* An unsupported overriding method is not reachable. */ - aResolved = null; - } - if (aResolved != null) { - /* - * aResolved == null means that the method in the base class was called, but never - * with this holder. - */ - if (includeInlinedMethods ? aResolved.isReachable() : aResolved.isImplementationInvoked()) { - implementations.add(aResolved); - } - } + AnalysisMethod resolved = method.resolveInType(holder, holderOrSubtypeInstantiated); + if (resolved != null && (includeInlinedMethods ? resolved.isReachable() : resolved.isImplementationInvoked())) { + implementations.add(resolved); } + return holderOrSubtypeInstantiated; } /** - * Collect and returns *all* subtypes of this type, not only the immediate subtypes. The - * immediate sub-types are updated continuously as the universe is expanded and can be accessed - * using {@link AnalysisType#getSubTypes()}. + * Collect and returns *all reachable* subtypes of this type, not only the immediate subtypes. + * To access the immediate sub-types use {@link AnalysisType#getSubTypes()}. + * + * Since the sub-types are updated continuously as the universe is expanded this method may + * return different results on each call, until the analysis universe reaches a stable state. */ - public static Set getAllSubtypes(AnalysisType baseType) { - HashSet result = new HashSet<>(); - collectSubtypes(baseType, result); + public static Set reachableSubtypes(AnalysisType baseType) { + Set result = baseType.getAllSubtypes(); + result.removeIf(t -> !t.isReachable()); return result; } - private static void collectSubtypes(AnalysisType baseType, Set result) { - for (AnalysisType subType : baseType.getSubTypes()) { - if (result.add(subType)) { - collectSubtypes(subType, result); - } - } - } - @Override public SnippetReflectionProvider getSnippetReflection() { return snippetReflection; @@ -691,6 +668,7 @@ public void onTypeInstantiated(AnalysisType type, UsageKind usage) { public void initializeType(AnalysisType type) { hostVM.initializeType(type); + type.onInitialized(); if (bb != null) { bb.onTypeInitialized(type); } @@ -716,6 +694,14 @@ public BigBang getBigbang() { return bb; } + public void setConcurrentAnalysisAccess(DuringAnalysisAccess access) { + this.concurrentAnalysisAccess = access; + } + + public DuringAnalysisAccess getConcurrentAnalysisAccess() { + return concurrentAnalysisAccess; + } + public void setHeapScanner(ImageHeapScanner heapScanner) { this.heapScanner = heapScanner; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AtomicUtils.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AtomicUtils.java index b036b8eb8cc3..bba47ea9a965 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AtomicUtils.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AtomicUtils.java @@ -31,10 +31,29 @@ public class AtomicUtils { + /** + * Atomically sets the value to {@code true} if the current value {@code == false}. + * + * @return {@code true} if successful. + */ public static boolean atomicMark(AtomicBoolean flag) { return flag.compareAndSet(false, true); } + /** + * Atomically sets the value to {@code true} if the current value {@code == false}. If + * successful, execute the task. + * + * @return {@code true} if successful. + */ + public static boolean atomicMarkAndRun(AtomicBoolean flag, Runnable task) { + boolean firstAttempt = flag.compareAndSet(false, true); + if (firstAttempt) { + task.run(); + } + return firstAttempt; + } + /** * Utility to lazily produce and initialize an object stored in an {@link AtomicReference}. The * {@code supplier} may be invoked multiple times, but the {@code initializer} is guaranteed to 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 bfa792ccf2b5..94e44eadb007 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 @@ -748,4 +748,7 @@ protected void onValueUpdate(EconomicMap, Object> values, String ol @Option(help = "Verify type states computed by the static analysis at run time. This is useful when diagnosing problems in the static analysis, but reduces peak performance significantly.", type = Debug)// public static final HostedOptionKey VerifyTypes = new HostedOptionKey<>(false); + + @Option(help = "Run reachability handlers concurrently during analysis.", type = Expert)// + public static final HostedOptionKey RunReachabilityHandlersConcurrently = new HostedOptionKey<>(true); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConcurrentReachabilityHandler.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConcurrentReachabilityHandler.java new file mode 100644 index 000000000000..9e418d537786 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConcurrentReachabilityHandler.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2022, 2022, 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 java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.graal.pointsto.meta.AnalysisElement; +import com.oracle.graal.pointsto.meta.AnalysisElement.ElementReachableNotification; +import com.oracle.graal.pointsto.meta.AnalysisElement.MethodOverrideReachableNotification; +import com.oracle.graal.pointsto.meta.AnalysisElement.SubtypeReachableNotification; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; + +@AutomaticFeature +public class ConcurrentReachabilityHandler implements ReachabilityHandler, Feature { + + private final Map, ElementReachableNotification> reachabilityNotifications = new ConcurrentHashMap<>(); + + public static ConcurrentReachabilityHandler singleton() { + return ImageSingletons.lookup(ConcurrentReachabilityHandler.class); + } + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return SubstrateOptions.RunReachabilityHandlersConcurrently.getValue(); + } + + @Override + public void registerMethodOverrideReachabilityHandler(BeforeAnalysisAccessImpl access, BiConsumer callback, Executable baseMethod) { + AnalysisMetaAccess metaAccess = access.getMetaAccess(); + AnalysisMethod baseAnalysisMethod = metaAccess.lookupJavaMethod(baseMethod); + + MethodOverrideReachableNotification notification = new MethodOverrideReachableNotification(callback); + baseAnalysisMethod.registerOverrideReachabilityNotification(notification); + + /* + * Notify for already reachable overrides. When a new override becomes reachable all + * installed reachability callbacks in the supertypes declaring the method are triggered. + */ + for (AnalysisMethod override : access.reachableMethodOverrides(baseAnalysisMethod)) { + notification.notifyCallback(metaAccess.getUniverse(), override); + } + } + + @Override + public void registerSubtypeReachabilityHandler(BeforeAnalysisAccessImpl access, BiConsumer> callback, Class baseClass) { + AnalysisMetaAccess metaAccess = access.getMetaAccess(); + AnalysisType baseType = metaAccess.lookupJavaType(baseClass); + + SubtypeReachableNotification notification = new SubtypeReachableNotification(callback); + baseType.registerSubtypeReachabilityNotification(notification); + + /* + * Notify for already reachable subtypes. When a new type becomes reachable all installed + * reachability callbacks in the supertypes are triggered. + */ + for (AnalysisType subtype : access.reachableSubtypes(baseType)) { + notification.notifyCallback(metaAccess.getUniverse(), subtype); + } + } + + @Override + public void registerClassInitializerReachabilityHandler(BeforeAnalysisAccessImpl access, Consumer callback, Class clazz) { + registerConcurrentReachabilityHandler(access, callback, new Class[]{clazz}, true); + } + + @Override + public void registerReachabilityHandler(BeforeAnalysisAccessImpl access, Consumer callback, Object[] triggers) { + registerConcurrentReachabilityHandler(access, callback, triggers, false); + } + + private void registerConcurrentReachabilityHandler(BeforeAnalysisAccessImpl access, Consumer callback, Object[] triggers, boolean triggerOnClassInitializer) { + AnalysisMetaAccess metaAccess = access.getMetaAccess(); + + /* + * All callback->notification pairs are tracked by the reachabilityNotifications map to + * prevent registering the same callback multiple times. The notifications are also tracked + * by each AnalysisElement, i.e., each trigger, and are removed as soon as they are + * notified. + */ + ElementReachableNotification notification = reachabilityNotifications.computeIfAbsent(callback, ElementReachableNotification::new); + + if (notification.isNotified()) { + /* Already notified from an earlier registration, nothing to do. */ + return; + } + + for (Object trigger : triggers) { + AnalysisElement analysisElement; + if (trigger instanceof Class) { + AnalysisType aType = metaAccess.lookupJavaType((Class) trigger); + analysisElement = triggerOnClassInitializer ? aType.getClassInitializer() : aType; + } else if (trigger instanceof Field) { + analysisElement = metaAccess.lookupJavaField((Field) trigger); + } else if (trigger instanceof Executable) { + analysisElement = metaAccess.lookupJavaMethod((Executable) trigger); + } else { + throw UserError.abort("registerReachabilityHandler called with an element that is not a Class, Field, Method, or Constructor: %s", trigger.getClass().getTypeName()); + } + + analysisElement.registerReachabilityNotification(notification); + if (analysisElement.isTriggered()) { + /* + * Element already triggered, just notify the callback. At this point we could just + * notify the callback and bail out, but, for debugging, it may be useful to execute + * the notification for each trigger. Note that although the notification can be + * shared between multiple triggers the notification mechanism ensures that the + * callback itself is only executed once. + */ + analysisElement.notifyReachabilityCallback(access.getUniverse(), notification); + } + } + } +} 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 3518ded1e095..aa73a49fdaff 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 @@ -64,6 +64,7 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.LinkerInvocation; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.graal.meta.RuntimeConfiguration; import com.oracle.svm.core.hub.DynamicHub; @@ -71,6 +72,7 @@ import com.oracle.svm.core.meta.SharedMethod; import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.meta.SubstrateObjectConstant; +import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.c.NativeLibraries; @@ -239,9 +241,7 @@ public Set> reachableSubtypes(Class baseClass) { } Set reachableSubtypes(AnalysisType baseType) { - Set result = AnalysisUniverse.getAllSubtypes(baseType); - result.removeIf(t -> !isReachable(t)); - return result; + return AnalysisUniverse.reachableSubtypes(baseType); } public Set reachableMethodOverrides(Executable baseMethod) { @@ -250,7 +250,7 @@ public Set reachableMethodOverrides(Executable baseMethod) { } Set reachableMethodOverrides(AnalysisMethod baseMethod) { - return AnalysisUniverse.getMethodImplementations(getBigBang(), baseMethod, true); + return AnalysisUniverse.reachableMethodOverrides(baseMethod); } public void rescanObject(Object obj) { @@ -326,10 +326,14 @@ public SVMHost getHostVM() { public static class BeforeAnalysisAccessImpl extends AnalysisAccessBase implements Feature.BeforeAnalysisAccess { private final NativeLibraries nativeLibraries; + private final boolean concurrentReachabilityHandlers; + private final ReachabilityHandler reachabilityHandler; public BeforeAnalysisAccessImpl(FeatureHandler featureHandler, ImageClassLoader imageClassLoader, Inflation bb, NativeLibraries nativeLibraries, DebugContext debugContext) { super(featureHandler, imageClassLoader, bb, debugContext); this.nativeLibraries = nativeLibraries; + this.concurrentReachabilityHandlers = SubstrateOptions.RunReachabilityHandlersConcurrently.getValue(bb.getOptions()); + this.reachabilityHandler = concurrentReachabilityHandlers ? ConcurrentReachabilityHandler.singleton() : ReachabilityHandlerFeature.singleton(); } public NativeLibraries getNativeLibraries() { @@ -445,22 +449,26 @@ public void registerHierarchyForReflectiveInstantiation(Class c) { @Override public void registerReachabilityHandler(Consumer callback, Object... elements) { - ReachabilityHandlerFeature.singleton().registerReachabilityHandler(this, callback, elements); + reachabilityHandler.registerReachabilityHandler(this, callback, elements); } @Override public void registerMethodOverrideReachabilityHandler(BiConsumer callback, Executable baseMethod) { - ReachabilityHandlerFeature.singleton().registerMethodOverrideReachabilityHandler(this, callback, baseMethod); + reachabilityHandler.registerMethodOverrideReachabilityHandler(this, callback, baseMethod); } @Override public void registerSubtypeReachabilityHandler(BiConsumer> callback, Class baseClass) { - ReachabilityHandlerFeature.singleton().registerSubtypeReachabilityHandler(this, callback, baseClass); + reachabilityHandler.registerSubtypeReachabilityHandler(this, callback, baseClass); } @Override public void registerClassInitializerReachabilityHandler(Consumer callback, Class clazz) { - ReachabilityHandlerFeature.singleton().registerClassInitializerReachabilityHandler(this, callback, clazz); + reachabilityHandler.registerClassInitializerReachabilityHandler(this, callback, clazz); + } + + public boolean concurrentReachabilityHandlers() { + return concurrentReachabilityHandlers; } } @@ -482,6 +490,25 @@ public boolean getAndResetRequireAnalysisIteration() { requireAnalysisIteration = false; return result; } + + } + + public static class ConcurrentAnalysisAccessImpl extends DuringAnalysisAccessImpl { + + private static final String concurrentReachabilityOption = SubstrateOptionsParser.commandArgument(SubstrateOptions.RunReachabilityHandlersConcurrently, "-"); + + public ConcurrentAnalysisAccessImpl(FeatureHandler featureHandler, ImageClassLoader imageClassLoader, Inflation bb, NativeLibraries nativeLibraries, DebugContext debugContext) { + super(featureHandler, imageClassLoader, bb, nativeLibraries, debugContext); + } + + @Override + public void requireAnalysisIteration() { + String msg = "Calling DuringAnalysisAccessImpl.requireAnalysisIteration() is not necessary when running the reachability handlers concurrently during analysis. " + + "To fallback to running the reachability handlers sequentially, i.e., from Feature.duringAnalysis(), you can add the " + concurrentReachabilityOption + + " option to the native-image command. Note that the fallback option is deprecated and it will be removed in a future release."; + throw VMError.shouldNotReachHere(msg); + } + } public static class AfterAnalysisAccessImpl extends AnalysisAccessBase implements Feature.AfterAnalysisAccess { 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 1d4589d2ab99..0abd5651efb0 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 @@ -223,6 +223,7 @@ import com.oracle.svm.hosted.FeatureImpl.BeforeCompilationAccessImpl; import com.oracle.svm.hosted.FeatureImpl.BeforeImageWriteAccessImpl; import com.oracle.svm.hosted.FeatureImpl.BeforeUniverseBuildingAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.ConcurrentAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.OnAnalysisExitAccessImpl; import com.oracle.svm.hosted.ProgressReporter.ReporterClosable; @@ -719,6 +720,8 @@ private boolean runPointsToAnalysis(String imageName, OptionValues options, Debu try (ReporterClosable c = ProgressReporter.singleton().printAnalysis(bb)) { DuringAnalysisAccessImpl config = new DuringAnalysisAccessImpl(featureHandler, loader, bb, nativeLibraries, debug); try { + ConcurrentAnalysisAccessImpl concurrentConfig = new ConcurrentAnalysisAccessImpl(featureHandler, loader, bb, nativeLibraries, debug); + aUniverse.setConcurrentAnalysisAccess(concurrentConfig); 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/ProtectionDomainFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProtectionDomainFeature.java index f15a6649dbe8..6c319001779e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProtectionDomainFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProtectionDomainFeature.java @@ -75,7 +75,9 @@ void enableCodeSource(DuringAnalysisAccess a) { ProtectionDomainSupport.enableCodeSource(); if (access != null) { access.rescanField(ImageSingletons.lookup(ProtectionDomainSupport.class), executableURLSupplierField); - access.requireAnalysisIteration(); + if (!access.concurrentReachabilityHandlers()) { + access.requireAnalysisIteration(); + } } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandler.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandler.java new file mode 100644 index 000000000000..35844167fe92 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022, 2022, 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 java.lang.reflect.Executable; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; + +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; + +public interface ReachabilityHandler { + + void registerMethodOverrideReachabilityHandler(BeforeAnalysisAccessImpl access, BiConsumer callback, Executable baseMethod); + + void registerSubtypeReachabilityHandler(BeforeAnalysisAccessImpl access, BiConsumer> callback, Class baseClass); + + void registerClassInitializerReachabilityHandler(BeforeAnalysisAccessImpl access, Consumer callback, Class clazz); + + void registerReachabilityHandler(BeforeAnalysisAccessImpl access, Consumer callback, Object[] triggers); +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandlerFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandlerFeature.java index 02353cfc2104..c9f2ae885af5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandlerFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReachabilityHandlerFeature.java @@ -42,6 +42,7 @@ import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; @@ -49,7 +50,7 @@ import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; @AutomaticFeature -public class ReachabilityHandlerFeature implements Feature { +public class ReachabilityHandlerFeature implements Feature, ReachabilityHandler { private final IdentityHashMap> activeHandlers = new IdentityHashMap<>(); private final IdentityHashMap>> triggeredHandlers = new IdentityHashMap<>(); @@ -58,19 +59,28 @@ public static ReachabilityHandlerFeature singleton() { return ImageSingletons.lookup(ReachabilityHandlerFeature.class); } + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return !SubstrateOptions.RunReachabilityHandlersConcurrently.getValue(); + } + + @Override public void registerMethodOverrideReachabilityHandler(BeforeAnalysisAccessImpl a, BiConsumer callback, Executable baseMethod) { registerReachabilityHandler(a, callback, new Executable[]{baseMethod}, false); } - public void registerSubtypeReachabilityHandler(BeforeAnalysisAccess a, BiConsumer> callback, Class baseClass) { + @Override + public void registerSubtypeReachabilityHandler(BeforeAnalysisAccessImpl a, BiConsumer> callback, Class baseClass) { registerReachabilityHandler(a, callback, new Class[]{baseClass}, false); } - public void registerClassInitializerReachabilityHandler(BeforeAnalysisAccess a, Consumer callback, Class clazz) { + @Override + public void registerClassInitializerReachabilityHandler(BeforeAnalysisAccessImpl a, Consumer callback, Class clazz) { registerReachabilityHandler(a, callback, new Class[]{clazz}, true); } - public void registerReachabilityHandler(BeforeAnalysisAccess a, Consumer callback, Object[] triggers) { + @Override + public void registerReachabilityHandler(BeforeAnalysisAccessImpl a, Consumer callback, Object[] triggers) { registerReachabilityHandler(a, callback, triggers, false); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrFeature.java index a20db7c5a48f..53defcff4fad 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrFeature.java @@ -248,7 +248,9 @@ private static void eventSubtypeReachable(DuringAnalysisAccess a, Class c) { Field f = c.getDeclaredField("eventHandler"); RuntimeReflection.register(f); access.rescanRoot(f); - a.requireAnalysisIteration(); + if (!access.concurrentReachabilityHandlers()) { + access.requireAnalysisIteration(); + } } catch (Exception e) { throw VMError.shouldNotReachHere("Unable to register eventHandler for: " + c.getCanonicalName(), e); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java index db8411e5ba60..990d89d889a5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java @@ -42,11 +42,14 @@ public abstract class XMLParsersRegistration extends JNIRegistrationUtil { - public void registerConfigs(Feature.DuringAnalysisAccess access) { + public void registerConfigs(Feature.DuringAnalysisAccess a) { + FeatureImpl.DuringAnalysisAccessImpl access = (FeatureImpl.DuringAnalysisAccessImpl) a; List parserClasses = xmlParserClasses(); - registerReflectionClasses((FeatureImpl.DuringAnalysisAccessImpl) access, parserClasses); + registerReflectionClasses(access, parserClasses); registerResources(); - access.requireAnalysisIteration(); + if (!access.concurrentReachabilityHandlers()) { + access.requireAnalysisIteration(); + } } abstract List xmlParserClasses(); diff --git a/substratevm/src/com.oracle.svm.methodhandles/src/com/oracle/svm/methodhandles/MethodHandleFeature.java b/substratevm/src/com.oracle.svm.methodhandles/src/com/oracle/svm/methodhandles/MethodHandleFeature.java index b52e76f71d4d..5a2699e54358 100644 --- a/substratevm/src/com.oracle.svm.methodhandles/src/com/oracle/svm/methodhandles/MethodHandleFeature.java +++ b/substratevm/src/com.oracle.svm.methodhandles/src/com/oracle/svm/methodhandles/MethodHandleFeature.java @@ -286,7 +286,7 @@ private static void registerUninitializedCallSiteForReflection(DuringAnalysisAcc RuntimeReflection.register(ReflectionUtil.lookupMethod(CallSite.class, "uninitializedCallSite", Object[].class)); } - private static void registerVarHandleMethodsForReflection(DuringAnalysisAccess access, Class subtype) { + private static void registerVarHandleMethodsForReflection(FeatureAccess access, Class subtype) { if (subtype.getPackage().getName().equals("java.lang.invoke") && subtype != access.findClassByName("java.lang.invoke.VarHandle")) { RuntimeReflection.register(subtype.getDeclaredMethods()); } @@ -383,7 +383,7 @@ public void duringAnalysis(DuringAnalysisAccess a) { access.rescanRoot(typedAccessors); } - private static void scanBoundMethodHandle(DuringAnalysisAccess a, Class bmhSubtype) { + private static void scanBoundMethodHandle(FeatureAccess a, Class bmhSubtype) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; Field bmhSpeciesField = ReflectionUtil.lookupField(true, bmhSubtype, "BMH_SPECIES"); if (bmhSpeciesField != null) { From 624e5f5eecc611022109cbbf6173a1708a875d79 Mon Sep 17 00:00:00 2001 From: Codrut Stancu Date: Wed, 30 Mar 2022 13:42:05 -0700 Subject: [PATCH 2/2] Make reachability handlers thread safe. --- .../svm/core/jdk/JNIRegistrationUtil.java | 4 ++-- .../svm/core/jdk/NativeLibrarySupport.java | 3 ++- .../svm/hosted/SecurityServicesFeature.java | 5 ++--- .../oracle/svm/hosted/c/NativeLibraries.java | 4 ++-- .../svm/truffle/TruffleBaseFeature.java | 21 +++++++++++++------ 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JNIRegistrationUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JNIRegistrationUtil.java index af0e6c0a1a20..aa441c5eb35f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JNIRegistrationUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JNIRegistrationUtil.java @@ -28,7 +28,6 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collections; -import java.util.IdentityHashMap; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; @@ -41,6 +40,7 @@ import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; import com.oracle.svm.core.jni.JNIRuntimeAccess; +import com.oracle.svm.core.util.ConcurrentIdentityHashMap; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; @@ -111,7 +111,7 @@ protected static void registerForThrowNew(FeatureAccess access, String... except } } - private static Set> runOnceCallbacks = Collections.newSetFromMap(new IdentityHashMap<>()); + private static final Set> runOnceCallbacks = Collections.newSetFromMap(new ConcurrentIdentityHashMap<>()); /** Intended to be used from within a callback to ensure that it is run only once. */ protected static boolean isRunOnce(Consumer callback) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibrarySupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibrarySupport.java index caf747a8d3fa..0b0e526281e8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibrarySupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/NativeLibrarySupport.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collections; import java.util.Deque; import java.util.List; import java.util.concurrent.locks.ReentrantLock; @@ -70,7 +71,7 @@ public static NativeLibrarySupport singleton() { private final ReentrantLock lock = new ReentrantLock(); - private final List knownLibraries = new ArrayList<>(); + private final List knownLibraries = Collections.synchronizedList(new ArrayList<>()); private final Deque currentLoadContext = new ArrayDeque<>(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 3f6e23b3ec3b..5fce04e6a3f4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -173,7 +173,7 @@ public static class Options { private Map> availableServices; /** All providers deemed to be used by this feature. */ - private final Set usedProviders = new HashSet<>(); + private final Set usedProviders = ConcurrentHashMap.newKeySet(); /** Providers marked as used by the user. */ private final Set manuallyMarkedUsedProviderClassNames = new HashSet<>(); @@ -684,8 +684,7 @@ private static void registerSpiClass(Method getSpiClassMethod, String serviceTyp } private void registerProvider(Provider provider) { - if (!usedProviders.contains(provider)) { - usedProviders.add(provider); + if (usedProviders.add(provider)) { registerForReflection(provider.getClass()); /* Trigger initialization of lazy field java.security.Provider.entrySet. */ provider.entrySet(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java index a5f24813d0a0..b3e91f5d362b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java @@ -165,12 +165,12 @@ public DependencyGraph() { public void add(String library, Collection dependencies) { UserError.guarantee(library != null, "The library name must be not null and not empty"); - Dependency libraryDependency = putWhenAbsent(library, new Dependency(library, new HashSet<>())); + Dependency libraryDependency = putWhenAbsent(library, new Dependency(library, ConcurrentHashMap.newKeySet())); Set collectedDependencies = libraryDependency.getDependencies(); for (String dependency : dependencies) { collectedDependencies.add(putWhenAbsent( - dependency, new Dependency(dependency, new HashSet<>()))); + dependency, new Dependency(dependency, ConcurrentHashMap.newKeySet()))); } } diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java index 5c5d2eac1fc4..7259d3deed48 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java @@ -43,6 +43,7 @@ import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; @@ -479,9 +480,17 @@ private void registerUnsafeAccess(DuringAnalysisAccess access, */ static class PossibleReplaceCandidatesSubtypeHandler implements BiConsumer> { - final List fields = new ArrayList<>(); + /** + * The fields are added serially, from the duringAnalysis phase which is run when the + * analysis reaches a local fix point, so no need for synchronization. + */ + List fields = new ArrayList<>(); final Class fieldType; - int candidateCount; + /** + * The candidates are counted from a reachability handler, which is run in parallel with the + * analysis. + */ + final AtomicInteger candidateCount = new AtomicInteger(0); PossibleReplaceCandidatesSubtypeHandler(Class fieldType) { this.fieldType = fieldType; @@ -489,7 +498,7 @@ static class PossibleReplaceCandidatesSubtypeHandler implements BiConsumer 1) { + if (candidateCount.get() > 1) { /* * Limit already reached no need to remember fields anymore we can directly register * them as unsafe accessed. @@ -517,13 +526,13 @@ public void accept(DuringAnalysisAccess t, Class u) { if (Modifier.isAbstract(u.getModifiers())) { return; } - candidateCount++; - if (candidateCount > 1) { + /* Limit reached, register the fields and clear the list. */ + if (candidateCount.incrementAndGet() == 2) { for (Field field : fields) { t.registerAsUnsafeAccessed(field); } - fields.clear(); + fields = null; } }