Skip to content

Commit ec7682d

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

File tree

11 files changed

+363
-425
lines changed

11 files changed

+363
-425
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/JNIJavaCalls.java

Lines changed: 0 additions & 42 deletions
This file was deleted.

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

Lines changed: 30 additions & 11 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,14 +64,15 @@
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;
7073
import com.oracle.svm.jni.hosted.JNICallSignature;
7174
import com.oracle.svm.jni.hosted.JNICallTrampolineMethod;
7275
import com.oracle.svm.jni.hosted.JNIFieldAccessorMethod;
73-
import com.oracle.svm.jni.hosted.JNIJavaCallMethod;
7476
import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod;
7577
import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod.CallVariant;
7678
import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod;
@@ -129,6 +131,12 @@ private void abortIfSealed() {
129131
UserError.guarantee(!sealed, "Classes, methods and fields must be registered for JNI access before the analysis has completed.");
130132
}
131133

134+
@Override
135+
public List<Class<? extends Feature>> getRequiredFeatures() {
136+
// Ensure that KnownOffsets is fully initialized before we access it
137+
return List.of(KnownOffsetsFeature.class);
138+
}
139+
132140
@Override
133141
public void afterRegistration(AfterRegistrationAccess arg) {
134142
AfterRegistrationAccessImpl access = (AfterRegistrationAccessImpl) arg;
@@ -178,8 +186,8 @@ public void beforeAnalysis(BeforeAnalysisAccess arg) {
178186
if (!ImageSingletons.contains(JNIFieldAccessorMethod.Factory.class)) {
179187
ImageSingletons.add(JNIFieldAccessorMethod.Factory.class, new JNIFieldAccessorMethod.Factory());
180188
}
181-
if (!ImageSingletons.contains(JNIJavaCallMethod.Factory.class)) {
182-
ImageSingletons.add(JNIJavaCallMethod.Factory.class, new JNIJavaCallMethod.Factory());
189+
if (!ImageSingletons.contains(JNIJavaCallWrapperMethod.Factory.class)) {
190+
ImageSingletons.add(JNIJavaCallWrapperMethod.Factory.class, new JNIJavaCallWrapperMethod.Factory());
183191
}
184192

185193
BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) arg;
@@ -302,22 +310,33 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) {
302310
JNIAccessibleMethodDescriptor descriptor = JNIAccessibleMethodDescriptor.of(method);
303311
jniClass.addMethodIfAbsent(descriptor, d -> {
304312
AnalysisUniverse universe = access.getUniverse();
305-
ResolvedJavaMethod targetMethod = universe.getOriginalMetaAccess().lookupJavaMethod(method);
306-
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);
313+
MetaAccessProvider originalMetaAccess = universe.getOriginalMetaAccess();
314+
ResolvedJavaMethod targetMethod = originalMetaAccess.lookupJavaMethod(method);
315+
316+
JNIJavaCallWrapperMethod.Factory factory = ImageSingletons.lookup(JNIJavaCallWrapperMethod.Factory.class);
317+
AnalysisMethod aTargetMethod = universe.lookup(targetMethod);
318+
if (!targetMethod.isConstructor() || factory.canInvokeConstructorOnObject(targetMethod, originalMetaAccess)) {
319+
access.registerAsRoot(aTargetMethod, false);
320+
} // else: function pointers will be an error stub
321+
322+
ResolvedJavaMethod newObjectMethod = null;
323+
if (targetMethod.isConstructor() && !targetMethod.getDeclaringClass().isAbstract()) {
324+
var aFactoryMethod = (AnalysisMethod) FactoryMethodSupport.singleton().lookup(access.getMetaAccess(), aTargetMethod, false);
325+
access.registerAsRoot(aFactoryMethod, true);
326+
newObjectMethod = aFactoryMethod.getWrapped();
327+
}
310328

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

315334
JNIJavaCallVariantWrapperGroup variantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), false);
316335
JNIJavaCallVariantWrapperGroup nonvirtualVariantWrappers = JNIJavaCallVariantWrapperGroup.NONE;
317336
if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isAbstract(method.getModifiers())) {
318337
nonvirtualVariantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), true);
319338
}
320-
return new JNIAccessibleMethod(d, method.getModifiers(), jniClass, javaCallMethod, callWrapperMethod,
339+
return new JNIAccessibleMethod(d, jniClass, targetMethod, newObjectMethod, callWrapperMethod,
321340
variantWrappers.varargs, variantWrappers.array, variantWrappers.valist,
322341
nonvirtualVariantWrappers.varargs, nonvirtualVariantWrappers.array, nonvirtualVariantWrappers.valist);
323342
});

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;
85+
private int vtableOffset = VTABLE_OFFSET_NOT_YET_COMPUTED;
86+
private CodePointer nonvirtualTarget;
87+
private PointerBase newObjectTarget; // for constructors
7888
private CodePointer callWrapper;
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 nonvirtualTarget;
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+
nonvirtualTarget = 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
}

0 commit comments

Comments
 (0)