Skip to content

[GR-50962] Add object reachability hooks. #8041

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,10 @@ protected void onObjectReachable(ImageHeapConstant imageHeapConstant, ScanReason
AnalysisType objectType = metaAccess.lookupJavaType(imageHeapConstant);
imageHeap.addReachableObject(objectType, imageHeapConstant);

AnalysisType type = imageHeapConstant.getType(metaAccess);
Object object = bb.getSnippetReflectionProvider().asObject(Object.class, imageHeapConstant);
type.notifyObjectReachable(universe.getConcurrentAnalysisAccess(), object);

markTypeInstantiated(objectType, reason);
if (imageHeapConstant instanceof ImageHeapObjectArray imageHeapArray) {
AnalysisType arrayType = imageHeapArray.getType(metaAccess);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider;
import com.oracle.graal.pointsto.infrastructure.WrappedJavaField;
import com.oracle.graal.pointsto.typestate.TypeState;
import com.oracle.graal.pointsto.util.AnalysisError;
import com.oracle.graal.pointsto.util.AnalysisFuture;
import com.oracle.graal.pointsto.util.AtomicUtils;
import com.oracle.graal.pointsto.util.ConcurrentLightHashSet;
Expand Down Expand Up @@ -476,7 +477,7 @@ public void setPosition(int newPosition) {
}

public int getPosition() {
assert position != -1 : this;
AnalysisError.guarantee(position != -1, "Unknown position for field %s", this);
return position;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

Expand Down Expand Up @@ -96,6 +97,9 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav
private static final AtomicReferenceFieldUpdater<AnalysisType, Object> instantiatedNotificationsUpdater = AtomicReferenceFieldUpdater
.newUpdater(AnalysisType.class, Object.class, "typeInstantiatedNotifications");

private static final AtomicReferenceFieldUpdater<AnalysisType, Object> objectReachableCallbacksUpdater = AtomicReferenceFieldUpdater
.newUpdater(AnalysisType.class, Object.class, "objectReachableCallbacks");

private static final AtomicReferenceFieldUpdater<AnalysisType, Object> isAllocatedUpdater = AtomicReferenceFieldUpdater
.newUpdater(AnalysisType.class, Object.class, "isAllocated");

Expand Down Expand Up @@ -219,6 +223,11 @@ public enum UsageKind {
*/
@SuppressWarnings("unused") private volatile Object typeInstantiatedNotifications;

/**
* Contains callbacks that are executed when an object of this type is marked as reachable.
*/
@SuppressWarnings("unused") private volatile Object objectReachableCallbacks;

@SuppressWarnings("this-escape")
public AnalysisType(AnalysisUniverse universe, ResolvedJavaType javaType, JavaKind storageKind, AnalysisType objectType, AnalysisType cloneableType) {
this.universe = universe;
Expand Down Expand Up @@ -614,6 +623,21 @@ public Set<MethodOverrideReachableNotification> getOverrideReachabilityNotificat
return ConcurrentLightHashMap.getOrDefault(this, overrideReachableNotificationsUpdater, method, Collections.emptySet());
}

public <T> void registerObjectReachableCallback(BiConsumer<DuringAnalysisAccess, T> callback) {
ConcurrentLightHashSet.addElement(this, objectReachableCallbacksUpdater, callback);
/* Register the callback with already discovered subtypes too. */
for (AnalysisType subType : subTypes) {
/* Subtypes include this type itself. */
if (!subType.equals(this)) {
subType.registerObjectReachableCallback(callback);
}
}
}

public <T> void notifyObjectReachable(DuringAnalysisAccess access, T object) {
ConcurrentLightHashSet.forEach(this, objectReachableCallbacksUpdater, (BiConsumer<DuringAnalysisAccess, T> c) -> c.accept(access, object));
}

public void registerInstantiatedCallback(Consumer<DuringAnalysisAccess> callback) {
if (this.isInstantiated()) {
/* If the type is already instantiated just trigger the callback. */
Expand Down Expand Up @@ -1013,6 +1037,11 @@ public Set<AnalysisType> getSubTypes() {

private void addSubType(AnalysisType subType) {
boolean result = this.subTypes.add(subType);
/* Register the object reachability callbacks with the newly discovered subtype. */
if (!subType.equals(this)) {
/* Subtypes include this type itself. */
ConcurrentLightHashSet.forEach(this, objectReachableCallbacksUpdater, (BiConsumer<DuringAnalysisAccess, Object> callback) -> subType.registerObjectReachableCallback(callback));
}
assert result : "Tried to add a " + subType + " which is already registered";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.jdk.InternalVMMethod;

import jdk.vm.ci.meta.ResolvedJavaMethod;

@InternalVMMethod
public abstract class SubstrateAccessor {
/**
Expand All @@ -43,6 +45,14 @@ public abstract class SubstrateAccessor {
@Platforms(Platform.HOSTED_ONLY.class) //
final Executable member;

/**
* The actual target method. For @{@link SubstrateConstructorAccessor} this is a factory method.
* For {@link SubstrateMethodAccessor} it can be the member itself or an adapter for caller
* sensitive methods.
*/
@Platforms(Platform.HOSTED_ONLY.class) //
final ResolvedJavaMethod targetMethod;

/**
* The first-level function that is invoked. It expands the boxed Object[] signature to the
* expanded real signature.
Expand All @@ -62,14 +72,24 @@ public abstract class SubstrateAccessor {
final DynamicHub initializeBeforeInvoke;

@Platforms(Platform.HOSTED_ONLY.class)
SubstrateAccessor(Executable member, CFunctionPointer expandSignature, CFunctionPointer directTarget, DynamicHub initializeBeforeInvoke) {
SubstrateAccessor(Executable member, CFunctionPointer expandSignature, CFunctionPointer directTarget, ResolvedJavaMethod targetMethod, DynamicHub initializeBeforeInvoke) {
this.member = member;
this.expandSignature = expandSignature;
this.directTarget = directTarget;
this.initializeBeforeInvoke = initializeBeforeInvoke;
this.targetMethod = targetMethod;
}

public Executable getMember() {
return member;
}

public CFunctionPointer getExpandSignature() {
return expandSignature;
}

@Platforms(Platform.HOSTED_ONLY.class)
public ResolvedJavaMethod getTargetMethod() {
return targetMethod;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@
import com.oracle.svm.core.reflect.ReflectionAccessorHolder.MethodInvokeFunctionPointer;

import jdk.internal.reflect.ConstructorAccessor;
import jdk.vm.ci.meta.ResolvedJavaMethod;

@InternalVMMethod
public final class SubstrateConstructorAccessor extends SubstrateAccessor implements ConstructorAccessor {

public SubstrateConstructorAccessor(Executable member, CFunctionPointer expandSignature, CFunctionPointer directTarget, DynamicHub initializeBeforeInvoke) {
super(member, expandSignature, directTarget, initializeBeforeInvoke);
public SubstrateConstructorAccessor(Executable member, CFunctionPointer expandSignature, CFunctionPointer directTarget, ResolvedJavaMethod targetMethod, DynamicHub initializeBeforeInvoke) {
super(member, expandSignature, directTarget, targetMethod, initializeBeforeInvoke);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@

import java.lang.reflect.Executable;

import jdk.graal.compiler.nodes.NamedLocationIdentity;
import jdk.graal.compiler.word.BarrieredAccess;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.c.function.CFunctionPointer;
Expand All @@ -39,7 +37,10 @@
import com.oracle.svm.core.reflect.ReflectionAccessorHolder.MethodInvokeFunctionPointerForCallerSensitiveAdapter;
import com.oracle.svm.core.util.VMError;

import jdk.graal.compiler.nodes.NamedLocationIdentity;
import jdk.graal.compiler.word.BarrieredAccess;
import jdk.internal.reflect.MethodAccessor;
import jdk.vm.ci.meta.ResolvedJavaMethod;

interface MethodAccessorJDK19 {
Object invoke(Object obj, Object[] args, Class<?> caller);
Expand All @@ -61,9 +62,9 @@ public final class SubstrateMethodAccessor extends SubstrateAccessor implements
private final boolean callerSensitiveAdapter;

@Platforms(Platform.HOSTED_ONLY.class)
public SubstrateMethodAccessor(Executable member, Class<?> receiverType, CFunctionPointer expandSignature, CFunctionPointer directTarget, int vtableOffset, DynamicHub initializeBeforeInvoke,
boolean callerSensitiveAdapter) {
super(member, expandSignature, directTarget, initializeBeforeInvoke);
public SubstrateMethodAccessor(Executable member, Class<?> receiverType, CFunctionPointer expandSignature, CFunctionPointer directTarget, ResolvedJavaMethod targetMethod, int vtableOffset,
DynamicHub initializeBeforeInvoke, boolean callerSensitiveAdapter) {
super(member, expandSignature, directTarget, targetMethod, initializeBeforeInvoke);
this.receiverType = receiverType;
this.vtableOffset = vtableOffset;
this.callerSensitiveAdapter = callerSensitiveAdapter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@
import java.util.stream.Collectors;

import org.graalvm.collections.Pair;
import jdk.graal.compiler.debug.DebugContext;
import jdk.graal.compiler.phases.util.Providers;
import org.graalvm.nativeimage.AnnotationAccess;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess;
Expand Down Expand Up @@ -89,6 +87,8 @@
import com.oracle.svm.util.ReflectionUtil;
import com.oracle.svm.util.UnsafePartitionKind;

import jdk.graal.compiler.debug.DebugContext;
import jdk.graal.compiler.phases.util.Providers;
import jdk.vm.ci.meta.MetaAccessProvider;

@SuppressWarnings("deprecation")
Expand Down Expand Up @@ -290,6 +290,16 @@ public void registerObjectReplacer(Function<Object, Object> replacer) {
getUniverse().registerObjectReplacer(replacer);
}

/**
* Register a callback that is executed when an object of the specified type or any of its
* subtypes is marked as reachable.
*
* @since 24.0
*/
public <T> void registerObjectReachableCallback(Class<T> clazz, BiConsumer<DuringAnalysisAccess, T> callback) {
getMetaAccess().lookupJavaType(clazz).registerObjectReachableCallback(callback);
}

public void registerSubstitutionProcessor(SubstitutionProcessor substitution) {
getUniverse().registerFeatureSubstitution(substitution);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import com.oracle.svm.hosted.FallbackFeature;
import com.oracle.svm.hosted.FeatureImpl;
import com.oracle.svm.hosted.FeatureImpl.BeforeCompilationAccessImpl;
import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl;
import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl;
import com.oracle.svm.hosted.ImageClassLoader;
import com.oracle.svm.hosted.analysis.Inflation;
Expand Down Expand Up @@ -163,6 +164,7 @@ public SubstrateAccessor getOrCreateAccessor(Executable member) {
private SubstrateAccessor createAccessor(Executable member) {
MethodPointer expandSignature;
MethodPointer directTarget = null;
AnalysisMethod targetMethod = null;
DynamicHub initializeBeforeInvoke = null;
if (member instanceof Method) {
int vtableOffset = SubstrateMethodAccessor.STATICALLY_BOUND;
Expand All @@ -171,7 +173,7 @@ private SubstrateAccessor createAccessor(Executable member) {

if (member.getDeclaringClass() == MethodHandle.class && (member.getName().equals("invoke") || member.getName().equals("invokeExact"))) {
/* Method handles must not be invoked via reflection. */
expandSignature = register(analysisAccess.getMetaAccess().lookupJavaMethod(methodHandleInvokeErrorMethod), "Registered in " + ReflectionFeature.class);
expandSignature = asMethodPointer(analysisAccess.getMetaAccess().lookupJavaMethod(methodHandleInvokeErrorMethod));
} else {
Method target = (Method) member;
try {
Expand All @@ -184,18 +186,17 @@ private SubstrateAccessor createAccessor(Executable member) {
throw VMError.shouldNotReachHere(ex);
}
expandSignature = createExpandSignatureMethod(target, callerSensitiveAdapter);
AnalysisMethod targetMethod = analysisAccess.getMetaAccess().lookupJavaMethod(target);
targetMethod = analysisAccess.getMetaAccess().lookupJavaMethod(target);
/*
* The SubstrateMethodAccessor is also used for the implementation of MethodHandle
* that are created to do an invokespecial. So non-abstract instance methods have
* both a directTarget and a vtableOffset.
*/
if (!targetMethod.isAbstract()) {
directTarget = register(targetMethod, "Reflection target, registered in " + ReflectionFeature.class);
directTarget = asMethodPointer(targetMethod);
}
if (!targetMethod.canBeStaticallyBound()) {
vtableOffset = SubstrateMethodAccessor.OFFSET_NOT_YET_COMPUTED;
analysisAccess.registerAsRoot(targetMethod, false, "Accessor method for reflection, registered in " + ReflectionFeature.class);
}
VMError.guarantee(directTarget != null || vtableOffset != SubstrateMethodAccessor.STATICALLY_BOUND, "Must have either a directTarget or a vtableOffset");
if (!targetMethod.isStatic()) {
Expand All @@ -205,7 +206,7 @@ private SubstrateAccessor createAccessor(Executable member) {
initializeBeforeInvoke = analysisAccess.getHostVM().dynamicHub(targetMethod.getDeclaringClass());
}
}
return new SubstrateMethodAccessor(member, receiverType, expandSignature, directTarget, vtableOffset, initializeBeforeInvoke, callerSensitiveAdapter);
return new SubstrateMethodAccessor(member, receiverType, expandSignature, directTarget, targetMethod, vtableOffset, initializeBeforeInvoke, callerSensitiveAdapter);

} else {
Class<?> holder = member.getDeclaringClass();
Expand All @@ -216,31 +217,30 @@ private SubstrateAccessor createAccessor(Executable member) {
* an interface, array, or primitive type, but we are defensive and throw the
* exception in that case too.
*/
expandSignature = register(analysisAccess.getMetaAccess().lookupJavaMethod(newInstanceErrorMethod), "Registered in " + ReflectionFeature.class);
expandSignature = asMethodPointer(analysisAccess.getMetaAccess().lookupJavaMethod(newInstanceErrorMethod));
} else {
expandSignature = createExpandSignatureMethod(member, false);
AnalysisMethod constructor = analysisAccess.getMetaAccess().lookupJavaMethod(member);
AnalysisMethod factoryMethod = FactoryMethodSupport.singleton().lookup(analysisAccess.getMetaAccess(), constructor, false);
directTarget = register(factoryMethod, "Factory method, registered in " + ReflectionFeature.class);
targetMethod = FactoryMethodSupport.singleton().lookup(analysisAccess.getMetaAccess(), constructor, false);
directTarget = asMethodPointer(targetMethod);
if (!constructor.getDeclaringClass().isInitialized()) {
initializeBeforeInvoke = analysisAccess.getHostVM().dynamicHub(constructor.getDeclaringClass());
}
}
return new SubstrateConstructorAccessor(member, expandSignature, directTarget, initializeBeforeInvoke);
return new SubstrateConstructorAccessor(member, expandSignature, directTarget, targetMethod, initializeBeforeInvoke);
}
}

private MethodPointer createExpandSignatureMethod(Executable member, boolean callerSensitiveAdapter) {
return expandSignatureMethods.computeIfAbsent(new SignatureKey(member, callerSensitiveAdapter), signatureKey -> {
ResolvedJavaMethod prototype = analysisAccess.getMetaAccess().lookupJavaMethod(callerSensitiveAdapter ? invokePrototypeForCallerSensitiveAdapter : invokePrototype).getWrapped();
return register(new ReflectionExpandSignatureMethod("invoke_" + signatureKey.uniqueShortName(), prototype, signatureKey.isStatic, signatureKey.argTypes, signatureKey.returnKind,
signatureKey.callerSensitiveAdapter), "Registered in " + ReflectionFeature.class);
return asMethodPointer(new ReflectionExpandSignatureMethod("invoke_" + signatureKey.uniqueShortName(), prototype, signatureKey.isStatic, signatureKey.argTypes, signatureKey.returnKind,
signatureKey.callerSensitiveAdapter));
});
}

private MethodPointer register(ResolvedJavaMethod method, String reason) {
private MethodPointer asMethodPointer(ResolvedJavaMethod method) {
AnalysisMethod aMethod = method instanceof AnalysisMethod ? (AnalysisMethod) method : analysisAccess.getUniverse().lookup(method);
analysisAccess.registerAsRoot(aMethod, true, reason);
return new MethodPointer(aMethod);
}

Expand Down Expand Up @@ -273,6 +273,27 @@ public void duringSetup(DuringSetupAccess a) {
for (Class<?> primitiveClass : PRIMITIVE_CLASSES) {
ClassForNameSupport.registerNegativeQuery(primitiveClass.getName());
}

access.registerObjectReachableCallback(SubstrateAccessor.class, ReflectionFeature::onAccessorReachable);
}

private static void onAccessorReachable(DuringAnalysisAccess a, SubstrateAccessor accessor) {
DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a;

String reason = "Registered in " + ReflectionFeature.class;
ResolvedJavaMethod expandSignatureMethod = ((MethodPointer) accessor.getExpandSignature()).getMethod();
access.registerAsRoot((AnalysisMethod) expandSignatureMethod, true, reason);

ResolvedJavaMethod targetMethod = accessor.getTargetMethod();
if (targetMethod != null) {
if (!targetMethod.isAbstract()) {
access.registerAsRoot((AnalysisMethod) targetMethod, true, reason);
}
/* If the accessor can be used for a virtual call, register virtual root method. */
if (accessor instanceof SubstrateMethodAccessor mAccessor && mAccessor.getVTableOffset() != SubstrateMethodAccessor.STATICALLY_BOUND) {
access.registerAsRoot((AnalysisMethod) targetMethod, false, reason);
}
}
}

@Override
Expand Down