Skip to content

Commit d03d387

Browse files
committed
Call Java methods from JNI via vtable or by address without per-method stubs.
1 parent bde96a7 commit d03d387

File tree

10 files changed

+345
-319
lines changed

10 files changed

+345
-319
lines changed

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
import com.oracle.svm.util.ReflectionUtil;
4848

4949
@AutomaticFeature
50-
final class KnownOffsetsFeature implements Feature {
50+
public final class KnownOffsetsFeature implements Feature {
5151
@Override
5252
public List<Class<? extends Feature>> getRequiredFeatures() {
5353
if (SubstrateOptions.MultiThreaded.getValue()) {

substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.lang.reflect.Modifier;
3131
import java.util.Arrays;
3232
import java.util.Collections;
33+
import java.util.List;
3334
import java.util.Map;
3435
import java.util.Set;
3536
import java.util.concurrent.ConcurrentHashMap;
@@ -63,7 +64,9 @@
6364
import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl;
6465
import com.oracle.svm.hosted.ProgressReporter;
6566
import com.oracle.svm.hosted.code.CEntryPointData;
67+
import com.oracle.svm.hosted.code.FactoryMethodSupport;
6668
import com.oracle.svm.hosted.config.ConfigurationParserUtils;
69+
import com.oracle.svm.hosted.meta.KnownOffsetsFeature;
6770
import com.oracle.svm.hosted.meta.MaterializedConstantFields;
6871
import com.oracle.svm.hosted.substitute.SubstitutionReflectivityFilter;
6972
import com.oracle.svm.jni.JNIJavaCallTrampolines;
@@ -129,6 +132,12 @@ private void abortIfSealed() {
129132
UserError.guarantee(!sealed, "Classes, methods and fields must be registered for JNI access before the analysis has completed.");
130133
}
131134

135+
@Override
136+
public List<Class<? extends Feature>> getRequiredFeatures() {
137+
// Ensure that KnownOffsets is fully initialized before we access it
138+
return List.of(KnownOffsetsFeature.class);
139+
}
140+
132141
@Override
133142
public void afterRegistration(AfterRegistrationAccess arg) {
134143
AfterRegistrationAccessImpl access = (AfterRegistrationAccessImpl) arg;
@@ -303,21 +312,27 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) {
303312
jniClass.addMethodIfAbsent(descriptor, d -> {
304313
AnalysisUniverse universe = access.getUniverse();
305314
ResolvedJavaMethod targetMethod = universe.getOriginalMetaAccess().lookupJavaMethod(method);
315+
AnalysisMethod aTargetMethod = universe.lookup(targetMethod);
316+
access.registerAsRoot(aTargetMethod, false);
317+
318+
ResolvedJavaMethod newObjectMethod = null;
319+
if (targetMethod.isConstructor() && !targetMethod.getDeclaringClass().isAbstract()) {
320+
var aFactoryMethod = (AnalysisMethod) FactoryMethodSupport.singleton().lookup(access.getMetaAccess(), aTargetMethod, false);
321+
access.registerAsRoot(aFactoryMethod, true);
322+
newObjectMethod = aFactoryMethod.getWrapped();
323+
}
306324

307-
WordTypes wordTypes = access.getBigBang().getProviders().getWordTypes();
308-
JNIJavaCallMethod javaCallMethod = ImageSingletons.lookup(JNIJavaCallMethod.Factory.class).create(targetMethod, universe, wordTypes);
309-
access.registerAsRoot(universe.lookup(javaCallMethod), true);
310-
311-
JNIJavaCallWrapperMethod callWrapperMethod = javaCallWrapperMethods.computeIfAbsent(javaCallMethod.getSignature(),
312-
signature -> new JNIJavaCallWrapperMethod(signature, universe.getOriginalMetaAccess(), wordTypes));
325+
JNICallSignature compatibleSignature = JNIJavaCallWrapperMethod.getGeneralizedSignatureForTarget(targetMethod, universe.getOriginalMetaAccess());
326+
JNIJavaCallWrapperMethod callWrapperMethod = javaCallWrapperMethods.computeIfAbsent(compatibleSignature,
327+
signature -> new JNIJavaCallWrapperMethod(signature, universe.getOriginalMetaAccess(), access.getBigBang().getProviders().getWordTypes()));
313328
access.registerAsRoot(universe.lookup(callWrapperMethod), true);
314329

315330
JNIJavaCallVariantWrapperGroup variantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), false);
316331
JNIJavaCallVariantWrapperGroup nonvirtualVariantWrappers = JNIJavaCallVariantWrapperGroup.NONE;
317332
if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isAbstract(method.getModifiers())) {
318333
nonvirtualVariantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), true);
319334
}
320-
return new JNIAccessibleMethod(d, method.getModifiers(), jniClass, javaCallMethod, callWrapperMethod,
335+
return new JNIAccessibleMethod(d, jniClass, targetMethod, newObjectMethod, callWrapperMethod,
321336
variantWrappers.varargs, variantWrappers.array, variantWrappers.valist,
322337
nonvirtualVariantWrappers.varargs, nonvirtualVariantWrappers.array, nonvirtualVariantWrappers.valist);
323338
});

substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleClass.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public final class JNIAccessibleClass {
4545
private EconomicMap<CharSequence, JNIAccessibleField> fields;
4646

4747
JNIAccessibleClass(Class<?> clazz) {
48+
assert clazz != null;
4849
this.classObject = clazz;
4950
}
5051

substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleMethod.java

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,24 @@
2626

2727
import java.lang.reflect.Modifier;
2828

29+
import org.graalvm.compiler.nodes.NamedLocationIdentity;
30+
import org.graalvm.compiler.word.BarrieredAccess;
2931
import org.graalvm.nativeimage.Platform.HOSTED_ONLY;
3032
import org.graalvm.nativeimage.Platforms;
3133
import org.graalvm.nativeimage.c.function.CFunctionPointer;
3234
import org.graalvm.nativeimage.c.function.CodePointer;
35+
import org.graalvm.word.PointerBase;
36+
import org.graalvm.word.WordFactory;
3337

3438
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
3539
import com.oracle.svm.core.annotate.AlwaysInline;
3640
import com.oracle.svm.core.annotate.Uninterruptible;
41+
import com.oracle.svm.core.graal.meta.KnownOffsets;
3742
import com.oracle.svm.core.meta.MethodPointer;
3843
import com.oracle.svm.core.util.VMError;
3944
import com.oracle.svm.hosted.FeatureImpl.CompilationAccessImpl;
45+
import com.oracle.svm.hosted.meta.HostedMethod;
4046
import com.oracle.svm.hosted.meta.HostedUniverse;
41-
import com.oracle.svm.jni.hosted.JNIJavaCallMethod;
4247
import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod;
4348
import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod.CallVariant;
4449
import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod;
@@ -53,6 +58,9 @@
5358
* Information on a method that can be looked up and called via JNI.
5459
*/
5560
public final class JNIAccessibleMethod extends JNIAccessibleMember {
61+
public static final int STATICALLY_BOUND_METHOD = -1;
62+
public static final int VTABLE_OFFSET_NOT_YET_COMPUTED = -2;
63+
public static final int NEW_OBJECT_INVALID_FOR_ABSTRACT_TYPE = -1;
5664

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

7583
@Platforms(HOSTED_ONLY.class) private final JNIAccessibleMethodDescriptor descriptor;
7684
private final int modifiers;
77-
private CodePointer javaCall;
7885
private CodePointer callWrapper;
86+
private int vtableOffset = VTABLE_OFFSET_NOT_YET_COMPUTED;
87+
private CodePointer directTarget;
88+
private PointerBase newObjectTarget; // for constructors
7989
@SuppressWarnings("unused") private CFunctionPointer varargsWrapper;
8090
@SuppressWarnings("unused") private CFunctionPointer arrayWrapper;
8191
@SuppressWarnings("unused") private CFunctionPointer valistWrapper;
8292
@SuppressWarnings("unused") private CFunctionPointer varargsNonvirtualWrapper;
8393
@SuppressWarnings("unused") private CFunctionPointer arrayNonvirtualWrapper;
8494
@SuppressWarnings("unused") private CFunctionPointer valistNonvirtualWrapper;
85-
@Platforms(HOSTED_ONLY.class) private final JNIJavaCallMethod javaCallMethod;
95+
@Platforms(HOSTED_ONLY.class) private final ResolvedJavaMethod targetMethod;
96+
@Platforms(HOSTED_ONLY.class) private final ResolvedJavaMethod newObjectTargetMethod;
8697
@Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod callWrapperMethod;
8798
@Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod varargsWrapperMethod;
8899
@Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod arrayWrapperMethod;
@@ -92,9 +103,9 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces
92103
@Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod valistNonvirtualWrapperMethod;
93104

94105
JNIAccessibleMethod(JNIAccessibleMethodDescriptor descriptor,
95-
int modifiers,
96106
JNIAccessibleClass declaringClass,
97-
JNIJavaCallMethod javaCallMethod,
107+
ResolvedJavaMethod targetMethod,
108+
ResolvedJavaMethod newObjectTargetMethod,
98109
JNIJavaCallWrapperMethod callWrapperMethod,
99110
JNIJavaCallVariantWrapperMethod varargsWrapper,
100111
JNIJavaCallVariantWrapperMethod arrayWrapper,
@@ -103,13 +114,14 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces
103114
JNIJavaCallVariantWrapperMethod arrayNonvirtualWrapper,
104115
JNIJavaCallVariantWrapperMethod valistNonvirtualWrapper) {
105116
super(declaringClass);
106-
assert javaCallMethod != null && callWrapperMethod != null && varargsWrapper != null && arrayWrapper != null && valistWrapper != null;
107-
assert (Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers)) //
117+
assert callWrapperMethod != null && varargsWrapper != null && arrayWrapper != null && valistWrapper != null;
118+
assert (targetMethod.isStatic() || targetMethod.isAbstract()) //
108119
? (varargsNonvirtualWrapper == null && arrayNonvirtualWrapper == null && valistNonvirtualWrapper == null)
109120
: (varargsNonvirtualWrapper != null & arrayNonvirtualWrapper != null && valistNonvirtualWrapper != null);
110121
this.descriptor = descriptor;
111-
this.modifiers = modifiers;
112-
this.javaCallMethod = javaCallMethod;
122+
this.modifiers = targetMethod.getModifiers();
123+
this.targetMethod = targetMethod;
124+
this.newObjectTargetMethod = newObjectTargetMethod;
113125
this.callWrapperMethod = callWrapperMethod;
114126
this.varargsWrapperMethod = varargsWrapper;
115127
this.arrayWrapperMethod = arrayWrapper;
@@ -119,18 +131,31 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces
119131
this.valistNonvirtualWrapperMethod = valistNonvirtualWrapper;
120132
}
121133

122-
@AlwaysInline("Work around an issue with the LLVM backend with which the return value was accessed incorrectly.")
123-
@Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
124-
public CodePointer getJavaCallAddress() {
125-
return javaCall;
126-
}
127-
128134
@AlwaysInline("Work around an issue with the LLVM backend with which the return value was accessed incorrectly.")
129135
@Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true)
130136
public CodePointer getCallWrapperAddress() {
131137
return callWrapper;
132138
}
133139

140+
@AlwaysInline("Work around an issue with the LLVM backend with which the return value was accessed incorrectly.")
141+
public CodePointer getJavaCallAddress(Object instance, boolean nonVirtual) {
142+
if (!nonVirtual) {
143+
assert vtableOffset != JNIAccessibleMethod.VTABLE_OFFSET_NOT_YET_COMPUTED;
144+
if (vtableOffset != JNIAccessibleMethod.STATICALLY_BOUND_METHOD) {
145+
return BarrieredAccess.readWord(instance.getClass(), vtableOffset, NamedLocationIdentity.FINAL_LOCATION);
146+
}
147+
}
148+
return directTarget;
149+
}
150+
151+
public PointerBase getNewObjectAddress() {
152+
return newObjectTarget;
153+
}
154+
155+
public Class<?> getDeclaringClassObject() {
156+
return getDeclaringClass().getClassObject();
157+
}
158+
134159
boolean isPublic() {
135160
return Modifier.isPublic(modifiers);
136161
}
@@ -143,7 +168,19 @@ boolean isStatic() {
143168
void finishBeforeCompilation(CompilationAccessImpl access) {
144169
HostedUniverse hUniverse = access.getUniverse();
145170
AnalysisUniverse aUniverse = access.getUniverse().getBigBang().getUniverse();
146-
javaCall = new MethodPointer(hUniverse.lookup(aUniverse.lookup(javaCallMethod)));
171+
HostedMethod hTarget = hUniverse.lookup(aUniverse.lookup(targetMethod));
172+
if (hTarget.canBeStaticallyBound()) {
173+
vtableOffset = STATICALLY_BOUND_METHOD;
174+
} else {
175+
vtableOffset = KnownOffsets.singleton().getVTableOffset(hTarget.getVTableIndex());
176+
}
177+
directTarget = new MethodPointer(hTarget);
178+
if (newObjectTargetMethod != null) {
179+
newObjectTarget = new MethodPointer(hUniverse.lookup(aUniverse.lookup(newObjectTargetMethod)));
180+
} else if (targetMethod.isConstructor()) {
181+
assert targetMethod.getDeclaringClass().isAbstract();
182+
newObjectTarget = WordFactory.signed(NEW_OBJECT_INVALID_FOR_ABSTRACT_TYPE);
183+
}
147184
callWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(callWrapperMethod)));
148185
varargsWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(varargsWrapperMethod)));
149186
arrayWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(arrayWrapperMethod)));

substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIReflectionDictionary.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ private static JNIMethodId toMethodID(JNIAccessibleMethod method) {
246246
return (JNIMethodId) value;
247247
}
248248

249+
@Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
249250
public static JNIAccessibleMethod getMethodByID(JNIMethodId method) {
250251
return (JNIAccessibleMethod) getObjectFromMethodID(method);
251252
}

substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallSignature.java

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import java.util.Arrays;
2828
import java.util.Objects;
2929

30+
import com.oracle.graal.pointsto.infrastructure.WrappedJavaType;
31+
import com.oracle.svm.core.SubstrateUtil;
32+
3033
import jdk.vm.ci.meta.JavaKind;
3134
import jdk.vm.ci.meta.JavaType;
3235
import jdk.vm.ci.meta.MetaAccessProvider;
@@ -35,60 +38,69 @@
3538

3639
public class JNICallSignature implements Signature {
3740

38-
private final JavaKind[] parameterKinds;
39-
private final JavaKind returnKind;
40-
private final MetaAccessProvider originalMetaAccess;
41+
private final JavaType[] paramTypes;
42+
private final JavaType returnType;
4143

42-
JNICallSignature(JavaKind[] parameterKinds, JavaKind returnKind, MetaAccessProvider originalMetaAccess) {
43-
this.parameterKinds = parameterKinds;
44-
this.returnKind = returnKind;
45-
this.originalMetaAccess = originalMetaAccess;
44+
JNICallSignature(JavaType[] paramTypes, JavaType returnType) {
45+
assert Arrays.stream(paramTypes).noneMatch(WrappedJavaType.class::isInstance) && !(returnType instanceof WrappedJavaType);
46+
this.paramTypes = paramTypes;
47+
this.returnType = returnType;
4648
}
4749

48-
public String getIdentifier() {
49-
StringBuilder sb = new StringBuilder(1 + parameterKinds.length);
50-
sb.append(returnKind.getTypeChar());
51-
for (JavaKind kind : parameterKinds) {
52-
sb.append(kind.getTypeChar());
50+
JNICallSignature(JavaKind[] paramKinds, JavaKind returnKind, MetaAccessProvider originalMetaAccess) {
51+
this.paramTypes = new ResolvedJavaType[paramKinds.length];
52+
for (int i = 0; i < paramKinds.length; i++) {
53+
this.paramTypes[i] = resolveType(paramKinds[i], originalMetaAccess);
5354
}
54-
return sb.toString();
55+
this.returnType = resolveType(returnKind, originalMetaAccess);
5556
}
5657

57-
@Override
58-
public int getParameterCount(boolean receiver) {
59-
return parameterKinds.length;
58+
private static ResolvedJavaType resolveType(JavaKind kind, MetaAccessProvider metaAccess) {
59+
return metaAccess.lookupJavaType(kind.isObject() ? Object.class : kind.toJavaClass());
6060
}
6161

62-
private ResolvedJavaType resolveType(JavaKind kind) {
63-
Class<?> clazz = Object.class;
64-
if (!kind.isObject()) {
65-
clazz = kind.toJavaClass();
62+
public String getIdentifier() {
63+
StringBuilder sb = new StringBuilder(1 + paramTypes.length);
64+
boolean digest = false;
65+
for (JavaType type : paramTypes) {
66+
if (type.getJavaKind().isPrimitive() || (type instanceof ResolvedJavaType && ((ResolvedJavaType) type).isJavaLangObject())) {
67+
sb.append(type.getJavaKind().getTypeChar());
68+
} else {
69+
sb.append(type.toClassName());
70+
digest = true;
71+
}
6672
}
67-
return originalMetaAccess.lookupJavaType(clazz);
73+
sb.append('_').append(returnType.getJavaKind().getTypeChar());
74+
return digest ? SubstrateUtil.digest(sb.toString()) : sb.toString();
75+
}
76+
77+
@Override
78+
public int getParameterCount(boolean receiver) {
79+
return paramTypes.length;
6880
}
6981

7082
@Override
7183
public JavaType getParameterType(int index, ResolvedJavaType accessingClass) {
72-
return resolveType(parameterKinds[index]);
84+
return paramTypes[index];
7385
}
7486

7587
@Override
7688
public JavaType getReturnType(ResolvedJavaType accessingClass) {
77-
return resolveType(returnKind);
89+
return returnType;
7890
}
7991

8092
@Override
8193
public boolean equals(Object obj) {
8294
if (this != obj && obj instanceof JNICallSignature) {
8395
var other = (JNICallSignature) obj;
84-
return Arrays.equals(parameterKinds, other.parameterKinds) && Objects.equals(returnKind, other.returnKind);
96+
return Arrays.equals(paramTypes, other.paramTypes) && Objects.equals(returnType, other.returnType);
8597
}
8698
return (this == obj);
8799
}
88100

89101
@Override
90102
public int hashCode() {
91-
return Arrays.hashCode(parameterKinds) * 31 + Objects.hashCode(returnKind);
103+
return Arrays.hashCode(paramTypes) * 31 + Objects.hashCode(returnType);
92104
}
93105

94106
@Override

0 commit comments

Comments
 (0)