Skip to content

Commit

Permalink
Call Java methods from JNI via vtable or by address without per-metho…
Browse files Browse the repository at this point in the history
…d stubs.
  • Loading branch information
peter-hofer committed Jul 7, 2022
1 parent bde96a7 commit ec7682d
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 425 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
import com.oracle.svm.util.ReflectionUtil;

@AutomaticFeature
final class KnownOffsetsFeature implements Feature {
public final class KnownOffsetsFeature implements Feature {
@Override
public List<Class<? extends Feature>> getRequiredFeatures() {
if (SubstrateOptions.MultiThreaded.getValue()) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -63,14 +64,15 @@
import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl;
import com.oracle.svm.hosted.ProgressReporter;
import com.oracle.svm.hosted.code.CEntryPointData;
import com.oracle.svm.hosted.code.FactoryMethodSupport;
import com.oracle.svm.hosted.config.ConfigurationParserUtils;
import com.oracle.svm.hosted.meta.KnownOffsetsFeature;
import com.oracle.svm.hosted.meta.MaterializedConstantFields;
import com.oracle.svm.hosted.substitute.SubstitutionReflectivityFilter;
import com.oracle.svm.jni.JNIJavaCallTrampolines;
import com.oracle.svm.jni.hosted.JNICallSignature;
import com.oracle.svm.jni.hosted.JNICallTrampolineMethod;
import com.oracle.svm.jni.hosted.JNIFieldAccessorMethod;
import com.oracle.svm.jni.hosted.JNIJavaCallMethod;
import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod;
import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod.CallVariant;
import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod;
Expand Down Expand Up @@ -129,6 +131,12 @@ private void abortIfSealed() {
UserError.guarantee(!sealed, "Classes, methods and fields must be registered for JNI access before the analysis has completed.");
}

@Override
public List<Class<? extends Feature>> getRequiredFeatures() {
// Ensure that KnownOffsets is fully initialized before we access it
return List.of(KnownOffsetsFeature.class);
}

@Override
public void afterRegistration(AfterRegistrationAccess arg) {
AfterRegistrationAccessImpl access = (AfterRegistrationAccessImpl) arg;
Expand Down Expand Up @@ -178,8 +186,8 @@ public void beforeAnalysis(BeforeAnalysisAccess arg) {
if (!ImageSingletons.contains(JNIFieldAccessorMethod.Factory.class)) {
ImageSingletons.add(JNIFieldAccessorMethod.Factory.class, new JNIFieldAccessorMethod.Factory());
}
if (!ImageSingletons.contains(JNIJavaCallMethod.Factory.class)) {
ImageSingletons.add(JNIJavaCallMethod.Factory.class, new JNIJavaCallMethod.Factory());
if (!ImageSingletons.contains(JNIJavaCallWrapperMethod.Factory.class)) {
ImageSingletons.add(JNIJavaCallWrapperMethod.Factory.class, new JNIJavaCallWrapperMethod.Factory());
}

BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) arg;
Expand Down Expand Up @@ -302,22 +310,33 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) {
JNIAccessibleMethodDescriptor descriptor = JNIAccessibleMethodDescriptor.of(method);
jniClass.addMethodIfAbsent(descriptor, d -> {
AnalysisUniverse universe = access.getUniverse();
ResolvedJavaMethod targetMethod = universe.getOriginalMetaAccess().lookupJavaMethod(method);

WordTypes wordTypes = access.getBigBang().getProviders().getWordTypes();
JNIJavaCallMethod javaCallMethod = ImageSingletons.lookup(JNIJavaCallMethod.Factory.class).create(targetMethod, universe, wordTypes);
access.registerAsRoot(universe.lookup(javaCallMethod), true);
MetaAccessProvider originalMetaAccess = universe.getOriginalMetaAccess();
ResolvedJavaMethod targetMethod = originalMetaAccess.lookupJavaMethod(method);

JNIJavaCallWrapperMethod.Factory factory = ImageSingletons.lookup(JNIJavaCallWrapperMethod.Factory.class);
AnalysisMethod aTargetMethod = universe.lookup(targetMethod);
if (!targetMethod.isConstructor() || factory.canInvokeConstructorOnObject(targetMethod, originalMetaAccess)) {
access.registerAsRoot(aTargetMethod, false);
} // else: function pointers will be an error stub

ResolvedJavaMethod newObjectMethod = null;
if (targetMethod.isConstructor() && !targetMethod.getDeclaringClass().isAbstract()) {
var aFactoryMethod = (AnalysisMethod) FactoryMethodSupport.singleton().lookup(access.getMetaAccess(), aTargetMethod, false);
access.registerAsRoot(aFactoryMethod, true);
newObjectMethod = aFactoryMethod.getWrapped();
}

JNIJavaCallWrapperMethod callWrapperMethod = javaCallWrapperMethods.computeIfAbsent(javaCallMethod.getSignature(),
signature -> new JNIJavaCallWrapperMethod(signature, universe.getOriginalMetaAccess(), wordTypes));
JNICallSignature compatibleSignature = JNIJavaCallWrapperMethod.getGeneralizedSignatureForTarget(targetMethod, originalMetaAccess);
JNIJavaCallWrapperMethod callWrapperMethod = javaCallWrapperMethods.computeIfAbsent(compatibleSignature,
signature -> factory.create(signature, originalMetaAccess, access.getBigBang().getProviders().getWordTypes()));
access.registerAsRoot(universe.lookup(callWrapperMethod), true);

JNIJavaCallVariantWrapperGroup variantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), false);
JNIJavaCallVariantWrapperGroup nonvirtualVariantWrappers = JNIJavaCallVariantWrapperGroup.NONE;
if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isAbstract(method.getModifiers())) {
nonvirtualVariantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), true);
}
return new JNIAccessibleMethod(d, method.getModifiers(), jniClass, javaCallMethod, callWrapperMethod,
return new JNIAccessibleMethod(d, jniClass, targetMethod, newObjectMethod, callWrapperMethod,
variantWrappers.varargs, variantWrappers.array, variantWrappers.valist,
nonvirtualVariantWrappers.varargs, nonvirtualVariantWrappers.array, nonvirtualVariantWrappers.valist);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public final class JNIAccessibleClass {
private EconomicMap<CharSequence, JNIAccessibleField> fields;

JNIAccessibleClass(Class<?> clazz) {
assert clazz != null;
this.classObject = clazz;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,24 @@

import java.lang.reflect.Modifier;

import org.graalvm.compiler.nodes.NamedLocationIdentity;
import org.graalvm.compiler.word.BarrieredAccess;
import org.graalvm.nativeimage.Platform.HOSTED_ONLY;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.c.function.CFunctionPointer;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.word.PointerBase;
import org.graalvm.word.WordFactory;

import com.oracle.graal.pointsto.meta.AnalysisUniverse;
import com.oracle.svm.core.annotate.AlwaysInline;
import com.oracle.svm.core.annotate.Uninterruptible;
import com.oracle.svm.core.graal.meta.KnownOffsets;
import com.oracle.svm.core.meta.MethodPointer;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.FeatureImpl.CompilationAccessImpl;
import com.oracle.svm.hosted.meta.HostedMethod;
import com.oracle.svm.hosted.meta.HostedUniverse;
import com.oracle.svm.jni.hosted.JNIJavaCallMethod;
import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod;
import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod.CallVariant;
import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod;
Expand All @@ -53,6 +58,9 @@
* Information on a method that can be looked up and called via JNI.
*/
public final class JNIAccessibleMethod extends JNIAccessibleMember {
public static final int STATICALLY_BOUND_METHOD = -1;
public static final int VTABLE_OFFSET_NOT_YET_COMPUTED = -2;
public static final int NEW_OBJECT_INVALID_FOR_ABSTRACT_TYPE = -1;

static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAccess, CallVariant variant, boolean nonVirtual) {
StringBuilder name = new StringBuilder(32);
Expand All @@ -74,15 +82,18 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces

@Platforms(HOSTED_ONLY.class) private final JNIAccessibleMethodDescriptor descriptor;
private final int modifiers;
private CodePointer javaCall;
private int vtableOffset = VTABLE_OFFSET_NOT_YET_COMPUTED;
private CodePointer nonvirtualTarget;
private PointerBase newObjectTarget; // for constructors
private CodePointer callWrapper;
@SuppressWarnings("unused") private CFunctionPointer varargsWrapper;
@SuppressWarnings("unused") private CFunctionPointer arrayWrapper;
@SuppressWarnings("unused") private CFunctionPointer valistWrapper;
@SuppressWarnings("unused") private CFunctionPointer varargsNonvirtualWrapper;
@SuppressWarnings("unused") private CFunctionPointer arrayNonvirtualWrapper;
@SuppressWarnings("unused") private CFunctionPointer valistNonvirtualWrapper;
@Platforms(HOSTED_ONLY.class) private final JNIJavaCallMethod javaCallMethod;
@Platforms(HOSTED_ONLY.class) private final ResolvedJavaMethod targetMethod;
@Platforms(HOSTED_ONLY.class) private final ResolvedJavaMethod newObjectTargetMethod;
@Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod callWrapperMethod;
@Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod varargsWrapperMethod;
@Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod arrayWrapperMethod;
Expand All @@ -92,9 +103,9 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces
@Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod valistNonvirtualWrapperMethod;

JNIAccessibleMethod(JNIAccessibleMethodDescriptor descriptor,
int modifiers,
JNIAccessibleClass declaringClass,
JNIJavaCallMethod javaCallMethod,
ResolvedJavaMethod targetMethod,
ResolvedJavaMethod newObjectTargetMethod,
JNIJavaCallWrapperMethod callWrapperMethod,
JNIJavaCallVariantWrapperMethod varargsWrapper,
JNIJavaCallVariantWrapperMethod arrayWrapper,
Expand All @@ -103,13 +114,14 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces
JNIJavaCallVariantWrapperMethod arrayNonvirtualWrapper,
JNIJavaCallVariantWrapperMethod valistNonvirtualWrapper) {
super(declaringClass);
assert javaCallMethod != null && callWrapperMethod != null && varargsWrapper != null && arrayWrapper != null && valistWrapper != null;
assert (Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers)) //
assert callWrapperMethod != null && varargsWrapper != null && arrayWrapper != null && valistWrapper != null;
assert (targetMethod.isStatic() || targetMethod.isAbstract()) //
? (varargsNonvirtualWrapper == null && arrayNonvirtualWrapper == null && valistNonvirtualWrapper == null)
: (varargsNonvirtualWrapper != null & arrayNonvirtualWrapper != null && valistNonvirtualWrapper != null);
this.descriptor = descriptor;
this.modifiers = modifiers;
this.javaCallMethod = javaCallMethod;
this.modifiers = targetMethod.getModifiers();
this.targetMethod = targetMethod;
this.newObjectTargetMethod = newObjectTargetMethod;
this.callWrapperMethod = callWrapperMethod;
this.varargsWrapperMethod = varargsWrapper;
this.arrayWrapperMethod = arrayWrapper;
Expand All @@ -119,18 +131,31 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces
this.valistNonvirtualWrapperMethod = valistNonvirtualWrapper;
}

@AlwaysInline("Work around an issue with the LLVM backend with which the return value was accessed incorrectly.")
@Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
public CodePointer getJavaCallAddress() {
return javaCall;
}

@AlwaysInline("Work around an issue with the LLVM backend with which the return value was accessed incorrectly.")
@Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true)
public CodePointer getCallWrapperAddress() {
return callWrapper;
}

@AlwaysInline("Work around an issue with the LLVM backend with which the return value was accessed incorrectly.")
public CodePointer getJavaCallAddress(Object instance, boolean nonVirtual) {
if (!nonVirtual) {
assert vtableOffset != JNIAccessibleMethod.VTABLE_OFFSET_NOT_YET_COMPUTED;
if (vtableOffset != JNIAccessibleMethod.STATICALLY_BOUND_METHOD) {
return BarrieredAccess.readWord(instance.getClass(), vtableOffset, NamedLocationIdentity.FINAL_LOCATION);
}
}
return nonvirtualTarget;
}

public PointerBase getNewObjectAddress() {
return newObjectTarget;
}

public Class<?> getDeclaringClassObject() {
return getDeclaringClass().getClassObject();
}

boolean isPublic() {
return Modifier.isPublic(modifiers);
}
Expand All @@ -143,7 +168,19 @@ boolean isStatic() {
void finishBeforeCompilation(CompilationAccessImpl access) {
HostedUniverse hUniverse = access.getUniverse();
AnalysisUniverse aUniverse = access.getUniverse().getBigBang().getUniverse();
javaCall = new MethodPointer(hUniverse.lookup(aUniverse.lookup(javaCallMethod)));
HostedMethod hTarget = hUniverse.lookup(aUniverse.lookup(targetMethod));
if (hTarget.canBeStaticallyBound()) {
vtableOffset = STATICALLY_BOUND_METHOD;
} else {
vtableOffset = KnownOffsets.singleton().getVTableOffset(hTarget.getVTableIndex());
}
nonvirtualTarget = new MethodPointer(hTarget);
if (newObjectTargetMethod != null) {
newObjectTarget = new MethodPointer(hUniverse.lookup(aUniverse.lookup(newObjectTargetMethod)));
} else if (targetMethod.isConstructor()) {
assert targetMethod.getDeclaringClass().isAbstract();
newObjectTarget = WordFactory.signed(NEW_OBJECT_INVALID_FOR_ABSTRACT_TYPE);
}
callWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(callWrapperMethod)));
varargsWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(varargsWrapperMethod)));
arrayWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(arrayWrapperMethod)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ private static JNIMethodId toMethodID(JNIAccessibleMethod method) {
return (JNIMethodId) value;
}

@Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
public static JNIAccessibleMethod getMethodByID(JNIMethodId method) {
return (JNIAccessibleMethod) getObjectFromMethodID(method);
}
Expand Down
Loading

0 comments on commit ec7682d

Please sign in to comment.