diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ThreadLocalHandles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ThreadLocalHandles.java
index e130eb05f228..0086739ac49c 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ThreadLocalHandles.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ThreadLocalHandles.java
@@ -49,6 +49,7 @@ public static U nullHandle() {
return WordFactory.signed(0);
}
+ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static boolean isInRange(U handle) {
return handle.rawValue() >= MIN_VALUE && handle.rawValue() <= MAX_VALUE;
}
@@ -90,13 +91,25 @@ private void growFrameStack() {
@SuppressWarnings("unchecked")
public T create(Object obj) {
if (obj == null) {
- return (T) nullHandle();
+ return nullHandle();
}
ensureCapacity(1);
+ T handle = tryCreateNonNull(obj);
+ assert !handle.equal(nullHandle());
+ return handle;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
+ public T tryCreateNonNull(Object obj) {
+ assert obj != null;
+ if (top >= objects.length) {
+ return nullHandle();
+ }
int index = top;
objects[index] = obj;
top++;
- return (T) WordFactory.signed(index);
+ return WordFactory.signed(index);
}
@SuppressWarnings("unchecked")
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SimpleSignature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SimpleSignature.java
index 29a81cf531d6..6e1b5254b892 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SimpleSignature.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SimpleSignature.java
@@ -24,9 +24,15 @@
*/
package com.oracle.svm.hosted.code;
+import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
+import com.oracle.svm.core.SubstrateUtil;
+
+import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.JavaType;
+import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaType;
import jdk.vm.ci.meta.Signature;
@@ -34,6 +40,19 @@
* A straightforward implementation of {@link Signature}.
*/
public class SimpleSignature implements Signature {
+ public static SimpleSignature fromKinds(JavaKind[] paramKinds, JavaKind returnKind, MetaAccessProvider metaAccess) {
+ ResolvedJavaType[] paramTypes = new ResolvedJavaType[paramKinds.length];
+ for (int i = 0; i < paramKinds.length; i++) {
+ paramTypes[i] = SimpleSignature.resolveType(paramKinds[i], metaAccess);
+ }
+ JavaType returnType = SimpleSignature.resolveType(returnKind, metaAccess);
+ return new SimpleSignature(paramTypes, returnType);
+ }
+
+ private static ResolvedJavaType resolveType(JavaKind kind, MetaAccessProvider metaAccess) {
+ return metaAccess.lookupJavaType(kind.isObject() ? Object.class : kind.toJavaClass());
+ }
+
private final JavaType[] parameterTypes;
private final JavaType returnType;
@@ -60,4 +79,38 @@ public JavaType getParameterType(int index, ResolvedJavaType accessingClass) {
public JavaType getReturnType(ResolvedJavaType accessingClass) {
return returnType;
}
+
+ public String getIdentifier() {
+ StringBuilder sb = new StringBuilder(1 + parameterTypes.length);
+ boolean digest = false;
+ for (JavaType type : parameterTypes) {
+ if (type.getJavaKind().isPrimitive() || (type instanceof ResolvedJavaType && ((ResolvedJavaType) type).isJavaLangObject())) {
+ sb.append(type.getJavaKind().getTypeChar());
+ } else {
+ sb.append(type.toClassName());
+ digest = true;
+ }
+ }
+ sb.append('_').append(returnType.getJavaKind().getTypeChar());
+ return digest ? SubstrateUtil.digest(sb.toString()) : sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this != obj && obj instanceof SimpleSignature) {
+ var other = (SimpleSignature) obj;
+ return Arrays.equals(parameterTypes, other.parameterTypes) && Objects.equals(returnType, other.returnType);
+ }
+ return (this == obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(parameterTypes) * 31 + Objects.hashCode(returnType);
+ }
+
+ @Override
+ public String toString() {
+ return getIdentifier();
+ }
}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java
index c79d3fcae7ab..564c7a5ee86e 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/KnownOffsetsFeature.java
@@ -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> getRequiredFeatures() {
if (SubstrateOptions.MultiThreaded.getValue()) {
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIGeneratedMethodSupport.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIGeneratedMethodSupport.java
index d8e2d5090e69..faf16e6e4edb 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIGeneratedMethodSupport.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIGeneratedMethodSupport.java
@@ -32,6 +32,7 @@
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.StaticFieldsSupport;
+import com.oracle.svm.core.annotate.Uninterruptible;
import com.oracle.svm.core.config.ConfigurationValues;
import com.oracle.svm.jni.access.JNIAccessibleField;
import com.oracle.svm.jni.access.JNINativeLinkage;
@@ -64,22 +65,27 @@ static JNIEnvironment environment() {
return JNIThreadLocalEnvironment.getAddress();
}
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
static JNIObjectHandle boxObjectInLocalHandle(Object obj) {
return JNIObjectHandles.createLocal(obj);
}
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
static Object unboxHandle(JNIObjectHandle handle) {
return JNIObjectHandles.getObject(handle);
}
+ @Uninterruptible(reason = "Allow inlining from field accessor methods, which are uninterruptible.", mayBeInlined = true)
static WordBase getFieldOffsetFromId(JNIFieldId fieldId) {
return JNIAccessibleField.getOffsetFromId(fieldId);
}
+ @Uninterruptible(reason = "Allow inlining from field accessor methods, which are uninterruptible.", mayBeInlined = true)
static Object getStaticPrimitiveFieldsArray() {
return StaticFieldsSupport.getStaticPrimitiveFields();
}
+ @Uninterruptible(reason = "Allow inlining from field accessor methods, which are uninterruptible.", mayBeInlined = true)
static Object getStaticObjectFieldsArray() {
return StaticFieldsSupport.getStaticObjectFields();
}
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCallTrampolines.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCallTrampolines.java
index ab56d6a52e51..62523b5e1d6c 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCallTrampolines.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCallTrampolines.java
@@ -29,7 +29,7 @@
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.jni.hosted.JNICallTrampolineMethod;
-import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod.CallVariant;
+import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod.CallVariant;
/** Holder class for generated {@link JNICallTrampolineMethod} code. */
public final class JNIJavaCallTrampolines {
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCallVariantWrappers.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCallVariantWrappers.java
new file mode 100644
index 000000000000..adaf3f7077e3
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCallVariantWrappers.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2017, 2021, 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.jni;
+
+import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod;
+
+import jdk.vm.ci.meta.ConstantPool;
+import jdk.vm.ci.meta.MetaAccessProvider;
+
+/** Holder class for generated {@link JNIJavaCallVariantWrapperMethod} code. */
+public final class JNIJavaCallVariantWrappers {
+ /**
+ * Generated call wrappers need an actual constant pool, so we provide that of our private
+ * constructor.
+ */
+ public static ConstantPool getConstantPool(MetaAccessProvider metaAccess) {
+ return metaAccess.lookupJavaType(JNIJavaCallVariantWrappers.class).getDeclaredConstructors()[0].getConstantPool();
+ }
+
+ private JNIJavaCallVariantWrappers() {
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIObjectHandles.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIObjectHandles.java
index 22e27758834d..e0f1cff442f3 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIObjectHandles.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIObjectHandles.java
@@ -25,6 +25,7 @@
package com.oracle.svm.jni;
import org.graalvm.compiler.api.replacements.Fold;
+import org.graalvm.compiler.nodes.extended.BranchProbabilityNode;
import org.graalvm.compiler.word.Word;
import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.Isolate;
@@ -112,22 +113,27 @@ private static ThreadLocalHandles createLocals() {
}
@SuppressWarnings("unchecked")
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
private static ThreadLocalHandles getExistingLocals() {
return handles.get();
}
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
private static boolean isInLocalRange(JNIObjectHandle handle) {
return ThreadLocalHandles.isInRange((ObjectHandle) handle);
}
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
private static ObjectHandle decodeLocal(JNIObjectHandle handle) {
return (ObjectHandle) handle;
}
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
private static JNIObjectHandle encodeLocal(ObjectHandle handle) {
return (JNIObjectHandle) handle;
}
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
public static T getObject(JNIObjectHandle handle) {
if (handle.equal(nullHandle())) {
return null;
@@ -138,10 +144,18 @@ public static T getObject(JNIObjectHandle handle) {
if (useImageHeapHandles() && JNIImageHeapHandles.isInRange(handle)) {
return JNIImageHeapHandles.getObject(handle);
}
+ return getObjectSlow(handle);
+ }
+
+ @Uninterruptible(reason = "Not really, but our caller is to allow inlining and we must be safe at this point.", calleeMustBe = false)
+ private static T getObjectSlow(JNIObjectHandle handle) {
+ return getObjectSlow0(handle);
+ }
+
+ private static T getObjectSlow0(JNIObjectHandle handle) {
if (JNIGlobalHandles.isInRange(handle)) {
return JNIGlobalHandles.getObject(handle);
}
-
throw throwIllegalArgumentException();
}
@@ -163,10 +177,30 @@ public static JNIObjectRefType getHandleType(JNIObjectHandle handle) {
return JNIObjectRefType.Invalid; // intentionally includes the null handle
}
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
public static JNIObjectHandle createLocal(Object obj) {
+ if (obj == null) {
+ return JNIObjectHandles.nullHandle();
+ }
if (useImageHeapHandles() && JNIImageHeapHandles.isInImageHeap(obj)) {
return JNIImageHeapHandles.asLocal(obj);
}
+ ThreadLocalHandles locals = getExistingLocals();
+ if (BranchProbabilityNode.probability(BranchProbabilityNode.VERY_FAST_PATH_PROBABILITY, locals != null)) {
+ ObjectHandle handle = locals.tryCreateNonNull(obj);
+ if (BranchProbabilityNode.probability(BranchProbabilityNode.FAST_PATH_PROBABILITY, handle.notEqual(nullHandle()))) {
+ return encodeLocal(handle);
+ }
+ }
+ return createLocalSlow(obj);
+ }
+
+ @Uninterruptible(reason = "Not really, but our caller is uninterruptible for inlining and we must be safe at this point.", calleeMustBe = false)
+ private static JNIObjectHandle createLocalSlow(Object obj) {
+ return createLocalSlow0(obj);
+ }
+
+ private static JNIObjectHandle createLocalSlow0(Object obj) {
return encodeLocal(getOrCreateLocals().create(obj));
}
@@ -269,6 +303,7 @@ final class JNIGlobalHandles {
private static final SignedWord MSB = WordFactory.signed(1L << 63);
private static final ObjectHandlesImpl globalHandles = new ObjectHandlesImpl(JNIObjectHandles.nullHandle().add(1), HANDLE_BITS_MASK, JNIObjectHandles.nullHandle());
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
static boolean isInRange(JNIObjectHandle handle) {
return MIN_VALUE.lessOrEqual((SignedWord) handle) && MAX_VALUE.greaterThan((SignedWord) handle);
}
@@ -355,15 +390,18 @@ final class JNIImageHeapHandles {
assert ENTIRE_RANGE_MAX.lessThan(JNIGlobalHandles.MIN_VALUE) || ENTIRE_RANGE_MIN.greaterThan(JNIGlobalHandles.MAX_VALUE);
}
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
static boolean isInImageHeap(Object target) {
return target != null && Heap.getHeap().isInImageHeap(target);
}
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
static boolean isInRange(JNIObjectHandle handle) {
SignedWord handleValue = (SignedWord) handle;
return handleValue.greaterOrEqual(ENTIRE_RANGE_MIN) && handleValue.lessOrEqual(ENTIRE_RANGE_MAX);
}
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
static JNIObjectHandle asLocal(Object target) {
assert isInImageHeap(target);
SignedWord base = (SignedWord) Isolates.getHeapBase(CurrentIsolate.getIsolate());
@@ -390,6 +428,7 @@ private static JNIObjectHandle toRange(JNIObjectHandle handle, SignedWord rangeM
return (JNIObjectHandle) ((SignedWord) handle).and(OBJ_OFFSET_BITS_MASK).add(rangeMin);
}
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
static T getObject(JNIObjectHandle handle) {
assert isInRange(handle);
UnsignedWord base = (UnsignedWord) Isolates.getHeapBase(CurrentIsolate.getIsolate());
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java
index a7db01f7ad52..b934dff00584 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java
@@ -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;
@@ -37,6 +38,7 @@
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.options.Option;
+import org.graalvm.compiler.word.WordTypes;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.impl.ConfigurationCondition;
@@ -46,6 +48,7 @@
import com.oracle.graal.pointsto.meta.AnalysisField;
import com.oracle.graal.pointsto.meta.AnalysisMethod;
import com.oracle.graal.pointsto.meta.AnalysisType;
+import com.oracle.graal.pointsto.meta.AnalysisUniverse;
import com.oracle.svm.core.configure.ConditionalElement;
import com.oracle.svm.core.configure.ConfigurationFile;
import com.oracle.svm.core.configure.ConfigurationFiles;
@@ -60,16 +63,19 @@
import com.oracle.svm.hosted.FeatureImpl.CompilationAccessImpl;
import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl;
import com.oracle.svm.hosted.ProgressReporter;
-import com.oracle.svm.hosted.c.NativeLibraries;
import com.oracle.svm.hosted.code.CEntryPointData;
+import com.oracle.svm.hosted.code.FactoryMethodSupport;
+import com.oracle.svm.hosted.code.SimpleSignature;
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.JNICallTrampolineMethod;
import com.oracle.svm.jni.hosted.JNIFieldAccessorMethod;
+import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod;
+import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod.CallVariant;
import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod;
-import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod.CallVariant;
import com.oracle.svm.util.ReflectionUtil;
import jdk.vm.ci.meta.MetaAccessProvider;
@@ -87,9 +93,26 @@ public static JNIAccessFeature singleton() {
return ImageSingletons.lookup(JNIAccessFeature.class);
}
+ /** A group of wrappers for the same target signature, but different JNI call variants. */
+ static final class JNIJavaCallVariantWrapperGroup {
+ static final JNIJavaCallVariantWrapperGroup NONE = new JNIJavaCallVariantWrapperGroup(null, null, null);
+
+ final JNIJavaCallVariantWrapperMethod varargs;
+ final JNIJavaCallVariantWrapperMethod array;
+ final JNIJavaCallVariantWrapperMethod valist;
+
+ JNIJavaCallVariantWrapperGroup(JNIJavaCallVariantWrapperMethod varargs, JNIJavaCallVariantWrapperMethod array, JNIJavaCallVariantWrapperMethod valist) {
+ this.varargs = varargs;
+ this.array = array;
+ this.valist = valist;
+ }
+ }
+
private boolean sealed = false;
- private NativeLibraries nativeLibraries;
private final Map trampolineMethods = new ConcurrentHashMap<>();
+ private final Map javaCallWrapperMethods = new ConcurrentHashMap<>();
+ private final Map callVariantWrappers = new ConcurrentHashMap<>();
+ private final Map nonvirtualCallVariantWrappers = new ConcurrentHashMap<>();
private int loadedConfigurations;
@@ -109,6 +132,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> 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;
@@ -163,7 +192,6 @@ public void beforeAnalysis(BeforeAnalysisAccess arg) {
}
BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) arg;
- this.nativeLibraries = access.getNativeLibraries();
for (CallVariant variant : CallVariant.values()) {
registerJavaCallTrampoline(access, variant, false);
@@ -180,7 +208,7 @@ private static ConditionalConfigurationRegistry getConditionalConfigurationRegis
private static void registerJavaCallTrampoline(BeforeAnalysisAccessImpl access, CallVariant variant, boolean nonVirtual) {
MetaAccessProvider originalMetaAccess = access.getMetaAccess().getWrapped();
- ResolvedJavaField field = JNIAccessibleMethod.getCallWrapperField(originalMetaAccess, variant, nonVirtual);
+ ResolvedJavaField field = JNIAccessibleMethod.getCallVariantWrapperField(originalMetaAccess, variant, nonVirtual);
access.getUniverse().lookup(field.getDeclaringClass()).registerAsReachable();
access.registerAsAccessed(access.getUniverse().lookup(field));
String name = JNIJavaCallTrampolines.getTrampolineName(variant, nonVirtual);
@@ -203,7 +231,7 @@ public JNICallTrampolineMethod getOrCreateCallTrampolineMethod(MetaAccessProvide
return trampolineMethods.computeIfAbsent(trampolineName, name -> {
Method reflectionMethod = ReflectionUtil.lookupMethod(JNIJavaCallTrampolines.class, name);
boolean nonVirtual = JNIJavaCallTrampolines.isNonVirtual(name);
- ResolvedJavaField field = JNIAccessibleMethod.getCallWrapperField(metaAccess, JNIJavaCallTrampolines.getVariant(name), nonVirtual);
+ ResolvedJavaField field = JNIAccessibleMethod.getCallVariantWrapperField(metaAccess, JNIJavaCallTrampolines.getVariant(name), nonVirtual);
ResolvedJavaMethod method = metaAccess.lookupJavaMethod(reflectionMethod);
return new JNICallTrampolineMethod(method, field, nonVirtual);
});
@@ -282,33 +310,55 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) {
JNIAccessibleClass jniClass = addClass(method.getDeclaringClass(), access);
JNIAccessibleMethodDescriptor descriptor = JNIAccessibleMethodDescriptor.of(method);
jniClass.addMethodIfAbsent(descriptor, d -> {
+ AnalysisUniverse universe = access.getUniverse();
+ MetaAccessProvider originalMetaAccess = universe.getOriginalMetaAccess();
+ ResolvedJavaMethod targetMethod = originalMetaAccess.lookupJavaMethod(method);
+
JNIJavaCallWrapperMethod.Factory factory = ImageSingletons.lookup(JNIJavaCallWrapperMethod.Factory.class);
- MetaAccessProvider wrappedMetaAccess = access.getMetaAccess().getWrapped();
+ 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 varargsCallWrapper = factory.create(method, CallVariant.VARARGS, false, wrappedMetaAccess, nativeLibraries);
- JNIJavaCallWrapperMethod arrayCallWrapper = factory.create(method, CallVariant.ARRAY, false, wrappedMetaAccess, nativeLibraries);
- JNIJavaCallWrapperMethod valistCallWrapper = factory.create(method, CallVariant.VA_LIST, false, wrappedMetaAccess, nativeLibraries);
- Stream wrappers = Stream.of(varargsCallWrapper, arrayCallWrapper, valistCallWrapper);
+ SimpleSignature 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);
- JNIJavaCallWrapperMethod varargsNonvirtualCallWrapper = null;
- JNIJavaCallWrapperMethod arrayNonvirtualCallWrapper = null;
- JNIJavaCallWrapperMethod valistNonvirtualCallWrapper = null;
+ JNIJavaCallVariantWrapperGroup variantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), false);
+ JNIJavaCallVariantWrapperGroup nonvirtualVariantWrappers = JNIJavaCallVariantWrapperGroup.NONE;
if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isAbstract(method.getModifiers())) {
- varargsNonvirtualCallWrapper = factory.create(method, CallVariant.VARARGS, true, wrappedMetaAccess, nativeLibraries);
- arrayNonvirtualCallWrapper = factory.create(method, CallVariant.ARRAY, true, wrappedMetaAccess, nativeLibraries);
- valistNonvirtualCallWrapper = factory.create(method, CallVariant.VA_LIST, true, wrappedMetaAccess, nativeLibraries);
- wrappers = Stream.concat(wrappers, Stream.of(varargsNonvirtualCallWrapper, arrayNonvirtualCallWrapper, valistNonvirtualCallWrapper));
+ nonvirtualVariantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), true);
}
+ return new JNIAccessibleMethod(d, jniClass, targetMethod, newObjectMethod, callWrapperMethod,
+ variantWrappers.varargs, variantWrappers.array, variantWrappers.valist,
+ nonvirtualVariantWrappers.varargs, nonvirtualVariantWrappers.array, nonvirtualVariantWrappers.valist);
+ });
+ }
- JNIAccessibleMethod jniMethod = new JNIAccessibleMethod(d, method.getModifiers(), jniClass, varargsCallWrapper, arrayCallWrapper, valistCallWrapper,
- varargsNonvirtualCallWrapper, arrayNonvirtualCallWrapper, valistNonvirtualCallWrapper);
+ private JNIJavaCallVariantWrapperGroup createJavaCallVariantWrappers(DuringAnalysisAccessImpl access, SimpleSignature wrapperSignature, boolean nonVirtual) {
+ var map = nonVirtual ? nonvirtualCallVariantWrappers : callVariantWrappers;
+ return map.computeIfAbsent(wrapperSignature, signature -> {
+ MetaAccessProvider originalMetaAccess = access.getUniverse().getOriginalMetaAccess();
+ WordTypes wordTypes = access.getBigBang().getProviders().getWordTypes();
+ var varargs = new JNIJavaCallVariantWrapperMethod(signature, CallVariant.VARARGS, nonVirtual, originalMetaAccess, wordTypes);
+ var array = new JNIJavaCallVariantWrapperMethod(signature, CallVariant.ARRAY, nonVirtual, originalMetaAccess, wordTypes);
+ var valist = new JNIJavaCallVariantWrapperMethod(signature, CallVariant.VA_LIST, nonVirtual, originalMetaAccess, wordTypes);
+ Stream wrappers = Stream.of(varargs, array, valist);
CEntryPointData unpublished = CEntryPointData.createCustomUnpublished();
wrappers.forEach(wrapper -> {
AnalysisMethod analysisWrapper = access.getUniverse().lookup(wrapper);
access.getBigBang().addRootMethod(analysisWrapper, true);
analysisWrapper.registerAsEntryPoint(unpublished); // ensures C calling convention
});
- return jniMethod;
+ return new JNIJavaCallVariantWrapperGroup(varargs, array, valist);
});
}
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleClass.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleClass.java
index c06d0b01045e..69ccde231558 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleClass.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleClass.java
@@ -45,6 +45,7 @@ public final class JNIAccessibleClass {
private EconomicMap fields;
JNIAccessibleClass(Class> clazz) {
+ assert clazz != null;
this.classObject = clazz;
}
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleField.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleField.java
index d93da05f3056..1bf468b08ec8 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleField.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleField.java
@@ -35,6 +35,7 @@
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.StaticFieldsSupport;
+import com.oracle.svm.core.annotate.Uninterruptible;
import com.oracle.svm.core.config.ObjectLayout;
import com.oracle.svm.hosted.FeatureImpl.CompilationAccessImpl;
import com.oracle.svm.hosted.config.HybridLayout;
@@ -64,6 +65,7 @@ public final class JNIAccessibleField extends JNIAccessibleMember {
* {@link StaticFieldsSupport#getStaticPrimitiveFields()} or
* {@link StaticFieldsSupport#getStaticObjectFields()}.
*/
+ @Uninterruptible(reason = "Allow inlining from field accessor methods, which are uninterruptible.", mayBeInlined = true)
public static WordBase getOffsetFromId(JNIFieldId id) {
UnsignedWord result = ((UnsignedWord) id).and(ID_OFFSET_MASK);
assert result.notEqual(0);
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleMethod.java
index de0b1b902bf3..a8735bc9e387 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleMethod.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleMethod.java
@@ -26,17 +26,28 @@
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.JNIJavaCallVariantWrapperMethod;
+import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod.CallVariant;
import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod;
-import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod.CallVariant;
+import com.oracle.svm.util.ReflectionUtil;
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaField;
@@ -47,8 +58,11 @@
* 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;
- public static ResolvedJavaField getCallWrapperField(MetaAccessProvider metaAccess, CallVariant variant, boolean nonVirtual) {
+ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAccess, CallVariant variant, boolean nonVirtual) {
StringBuilder name = new StringBuilder(32);
if (variant == CallVariant.VARARGS) {
name.append("varargs");
@@ -62,59 +76,91 @@ public static ResolvedJavaField getCallWrapperField(MetaAccessProvider metaAcces
if (nonVirtual) {
name.append("Nonvirtual");
}
- name.append("CallWrapper");
- try {
- return metaAccess.lookupJavaField(JNIAccessibleMethod.class.getDeclaredField(name.toString()));
- } catch (NoSuchFieldException e) {
- throw VMError.shouldNotReachHere(e);
- }
+ name.append("Wrapper");
+ return metaAccess.lookupJavaField(ReflectionUtil.lookupField(JNIAccessibleMethod.class, name.toString()));
}
@Platforms(HOSTED_ONLY.class) private final JNIAccessibleMethodDescriptor descriptor;
private final int modifiers;
- @SuppressWarnings("unused") private CFunctionPointer varargsCallWrapper;
- @SuppressWarnings("unused") private CFunctionPointer arrayCallWrapper;
- @SuppressWarnings("unused") private CFunctionPointer valistCallWrapper;
- @SuppressWarnings("unused") private CFunctionPointer varargsNonvirtualCallWrapper;
- @SuppressWarnings("unused") private CFunctionPointer arrayNonvirtualCallWrapper;
- @SuppressWarnings("unused") private CFunctionPointer valistNonvirtualCallWrapper;
- @Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod varargsCallWrapperMethod;
- @Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod arrayCallWrapperMethod;
- @Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod valistCallWrapperMethod;
- @Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod varargsNonvirtualCallWrapperMethod;
- @Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod arrayNonvirtualCallWrapperMethod;
- @Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod valistNonvirtualCallWrapperMethod;
+ 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 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;
+ @Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod valistWrapperMethod;
+ @Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod varargsNonvirtualWrapperMethod;
+ @Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod arrayNonvirtualWrapperMethod;
+ @Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod valistNonvirtualWrapperMethod;
JNIAccessibleMethod(JNIAccessibleMethodDescriptor descriptor,
- int modifiers,
JNIAccessibleClass declaringClass,
- JNIJavaCallWrapperMethod varargsCallWrapper,
- JNIJavaCallWrapperMethod arrayCallWrapper,
- JNIJavaCallWrapperMethod valistCallWrapper,
- JNIJavaCallWrapperMethod varargsNonvirtualCallWrapperMethod,
- JNIJavaCallWrapperMethod arrayNonvirtualCallWrapperMethod,
- JNIJavaCallWrapperMethod valistNonvirtualCallWrapperMethod) {
+ ResolvedJavaMethod targetMethod,
+ ResolvedJavaMethod newObjectTargetMethod,
+ JNIJavaCallWrapperMethod callWrapperMethod,
+ JNIJavaCallVariantWrapperMethod varargsWrapper,
+ JNIJavaCallVariantWrapperMethod arrayWrapper,
+ JNIJavaCallVariantWrapperMethod valistWrapper,
+ JNIJavaCallVariantWrapperMethod varargsNonvirtualWrapper,
+ JNIJavaCallVariantWrapperMethod arrayNonvirtualWrapper,
+ JNIJavaCallVariantWrapperMethod valistNonvirtualWrapper) {
super(declaringClass);
-
- assert varargsCallWrapper != null && arrayCallWrapper != null && valistCallWrapper != null;
- assert (Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers)) //
- ? (varargsNonvirtualCallWrapperMethod == null && arrayNonvirtualCallWrapperMethod == null && valistNonvirtualCallWrapperMethod == null)
- : (varargsNonvirtualCallWrapperMethod != null & arrayNonvirtualCallWrapperMethod != null && valistNonvirtualCallWrapperMethod != null);
+ 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.varargsCallWrapperMethod = varargsCallWrapper;
- this.arrayCallWrapperMethod = arrayCallWrapper;
- this.valistCallWrapperMethod = valistCallWrapper;
- this.varargsNonvirtualCallWrapperMethod = varargsNonvirtualCallWrapperMethod;
- this.arrayNonvirtualCallWrapperMethod = arrayNonvirtualCallWrapperMethod;
- this.valistNonvirtualCallWrapperMethod = valistNonvirtualCallWrapperMethod;
+ this.modifiers = targetMethod.getModifiers();
+ this.targetMethod = targetMethod;
+ this.newObjectTargetMethod = newObjectTargetMethod;
+ this.callWrapperMethod = callWrapperMethod;
+ this.varargsWrapperMethod = varargsWrapper;
+ this.arrayWrapperMethod = arrayWrapper;
+ this.valistWrapperMethod = valistWrapper;
+ this.varargsNonvirtualWrapperMethod = varargsNonvirtualWrapper;
+ this.arrayNonvirtualWrapperMethod = arrayNonvirtualWrapper;
+ 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 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();
}
- public boolean isPublic() {
+ boolean isPublic() {
return Modifier.isPublic(modifiers);
}
- public boolean isStatic() {
+ boolean isStatic() {
return Modifier.isStatic(modifiers);
}
@@ -122,13 +168,27 @@ public boolean isStatic() {
void finishBeforeCompilation(CompilationAccessImpl access) {
HostedUniverse hUniverse = access.getUniverse();
AnalysisUniverse aUniverse = access.getUniverse().getBigBang().getUniverse();
- varargsCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(varargsCallWrapperMethod)));
- arrayCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(arrayCallWrapperMethod)));
- valistCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(valistCallWrapperMethod)));
+ 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)));
+ valistWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(valistWrapperMethod)));
if (!Modifier.isStatic(modifiers) && !Modifier.isAbstract(modifiers)) {
- varargsNonvirtualCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(varargsNonvirtualCallWrapperMethod)));
- arrayNonvirtualCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(arrayNonvirtualCallWrapperMethod)));
- valistNonvirtualCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(valistNonvirtualCallWrapperMethod)));
+ varargsNonvirtualWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(varargsNonvirtualWrapperMethod)));
+ arrayNonvirtualWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(arrayNonvirtualWrapperMethod)));
+ valistNonvirtualWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(valistNonvirtualWrapperMethod)));
}
setHidingSubclasses(access.getMetaAccess(), this::anyMatchIgnoreReturnType);
}
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIReflectionDictionary.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIReflectionDictionary.java
index 2293ad1b3b8c..dde3ac6e32d0 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIReflectionDictionary.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIReflectionDictionary.java
@@ -45,6 +45,7 @@
import com.oracle.svm.core.Isolates;
import com.oracle.svm.core.SubstrateOptions;
+import com.oracle.svm.core.annotate.Uninterruptible;
import com.oracle.svm.core.log.Log;
import com.oracle.svm.core.util.ImageHeapMap;
import com.oracle.svm.core.util.Utf8.WrappedAsciiCString;
@@ -245,16 +246,18 @@ 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) {
- Object obj = null;
- if (method.notEqual(WordFactory.zero())) {
- Pointer p = (Pointer) method;
- if (SubstrateOptions.SpawnIsolates.getValue()) {
- p = p.add((UnsignedWord) Isolates.getHeapBase(CurrentIsolate.getIsolate()));
- }
- obj = p.toObject();
+ return (JNIAccessibleMethod) getObjectFromMethodID(method);
+ }
+
+ @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true)
+ public static Object getObjectFromMethodID(JNIMethodId method) {
+ Pointer p = (Pointer) method;
+ if (SubstrateOptions.SpawnIsolates.getValue()) {
+ p = p.add((UnsignedWord) Isolates.getHeapBase(CurrentIsolate.getIsolate()));
}
- return (JNIAccessibleMethod) obj;
+ return p.toObject();
}
private JNIAccessibleField getDeclaredField(Class> classObject, CharSequence name, boolean isStatic, String dumpLabel) {
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/functions/JNIFunctionTablesFeature.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/functions/JNIFunctionTablesFeature.java
index 8c49fb869472..7499794b933f 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/functions/JNIFunctionTablesFeature.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/functions/JNIFunctionTablesFeature.java
@@ -34,7 +34,6 @@
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.function.CFunctionPointer;
import org.graalvm.nativeimage.hosted.Feature;
-import com.oracle.svm.util.GuardedAnnotationAccess;
import com.oracle.graal.pointsto.meta.AnalysisMetaAccess;
import com.oracle.graal.pointsto.meta.AnalysisMethod;
@@ -61,11 +60,12 @@
import com.oracle.svm.jni.functions.JNIFunctions.UnimplementedWithJavaVMArgument;
import com.oracle.svm.jni.hosted.JNICallTrampolineMethod;
import com.oracle.svm.jni.hosted.JNIFieldAccessorMethod;
-import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod.CallVariant;
+import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod.CallVariant;
import com.oracle.svm.jni.hosted.JNIPrimitiveArrayOperationMethod;
import com.oracle.svm.jni.hosted.JNIPrimitiveArrayOperationMethod.Operation;
import com.oracle.svm.jni.nativeapi.JNIInvokeInterface;
import com.oracle.svm.jni.nativeapi.JNINativeInterface;
+import com.oracle.svm.util.GuardedAnnotationAccess;
import jdk.vm.ci.meta.ConstantPool;
import jdk.vm.ci.meta.JavaKind;
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/functions/JNIFunctions.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/functions/JNIFunctions.java
index d7410f8d36e8..686a79d32de6 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/functions/JNIFunctions.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/functions/JNIFunctions.java
@@ -48,7 +48,6 @@
import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer;
import org.graalvm.nativeimage.c.struct.SizeOf;
import org.graalvm.nativeimage.c.type.CCharPointer;
-import org.graalvm.nativeimage.c.type.CLongPointer;
import org.graalvm.nativeimage.c.type.CShortPointer;
import org.graalvm.nativeimage.c.type.CTypeConversion;
import org.graalvm.nativeimage.c.type.WordPointer;
@@ -111,6 +110,7 @@
import com.oracle.svm.jni.nativeapi.JNINativeMethod;
import com.oracle.svm.jni.nativeapi.JNIObjectHandle;
import com.oracle.svm.jni.nativeapi.JNIObjectRefType;
+import com.oracle.svm.jni.nativeapi.JNIValue;
import com.oracle.svm.jni.nativeapi.JNIVersion;
import jdk.internal.misc.Unsafe;
@@ -864,7 +864,7 @@ static int Throw(JNIEnvironment env, JNIObjectHandle handle) throws Throwable {
interface NewObjectWithObjectArrayArgFunctionPointer extends CFunctionPointer {
@InvokeCFunctionPointer
- JNIObjectHandle invoke(JNIEnvironment env, JNIObjectHandle clazz, JNIMethodId ctor, CLongPointer array);
+ JNIObjectHandle invoke(JNIEnvironment env, JNIObjectHandle clazz, JNIMethodId ctor, JNIValue array);
}
/*
@@ -883,8 +883,8 @@ static int ThrowNew(JNIEnvironment env, JNIObjectHandle clazzHandle, CCharPointe
* instead.
*/
NewObjectWithObjectArrayArgFunctionPointer newObjectA = (NewObjectWithObjectArrayArgFunctionPointer) env.getFunctions().getNewObjectA();
- CLongPointer array = StackValue.get(Long.BYTES);
- array.write(messageHandle.rawValue());
+ JNIValue array = StackValue.get(JNIValue.class);
+ array.setObject(messageHandle);
JNIObjectHandle exception = newObjectA.invoke(env, clazzHandle, ctor, array);
throw (Throwable) JNIObjectHandles.getObject(exception);
}
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallTrampolineMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallTrampolineMethod.java
index 3f56b06582ba..ee637338d00b 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallTrampolineMethod.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallTrampolineMethod.java
@@ -67,20 +67,20 @@
* NativeType CallMethod(JNIEnv *env, jobject obj, jmethodID methodID, ...);
*
*
- * The {@code jmethodID} values that we pass out are the addresses of {@link JNIAccessibleMethod}
- * objects, which are made immutable so that they are never moved by the garbage collector. The
- * trampoline simply jumps to the address of a specific call wrapper that is stored in a
- * {@link #callWrapperField field} of the object. The wrappers then take care of spilling
+ * The {@code jmethodID} values that we pass out are the image heap offsets (or addresses) of
+ * {@link JNIAccessibleMethod} objects, which are immutable so they are never moved by the garbage
+ * collector. The trampoline simply jumps to the address of a specific call wrapper that is stored
+ * in a {@link #jumpAddressField field} of the object. The wrappers then take care of spilling
* callee-saved registers, transitioning from native to Java and back, obtaining the arguments in a
* particular form (varargs, array, va_list) and boxing/unboxing object handles as necessary.
*/
public class JNICallTrampolineMethod extends CustomSubstitutionMethod {
- private final ResolvedJavaField callWrapperField;
+ private final ResolvedJavaField jumpAddressField;
private final boolean nonVirtual;
- public JNICallTrampolineMethod(ResolvedJavaMethod original, ResolvedJavaField callWrapperField, boolean nonVirtual) {
+ public JNICallTrampolineMethod(ResolvedJavaMethod original, ResolvedJavaField jumpAddressField, boolean nonVirtual) {
super(original);
- this.callWrapperField = callWrapperField;
+ this.jumpAddressField = jumpAddressField;
this.nonVirtual = nonVirtual;
}
@@ -139,7 +139,7 @@ private int getFieldOffset(HostedProviders providers) {
HostedMetaAccess metaAccess = (HostedMetaAccess) providers.getMetaAccess();
HostedUniverse universe = metaAccess.getUniverse();
AnalysisUniverse analysisUniverse = universe.getBigBang().getUniverse();
- HostedField hostedField = universe.lookup(analysisUniverse.lookup(callWrapperField));
+ HostedField hostedField = universe.lookup(analysisUniverse.lookup(jumpAddressField));
assert hostedField.hasLocation();
return hostedField.getLocation();
}
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallWrapperFeature.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallWrapperFeature.java
index 3e70c9182d3f..3d578ce5d0f3 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallWrapperFeature.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallWrapperFeature.java
@@ -27,7 +27,6 @@
import java.util.Arrays;
import java.util.List;
-import org.graalvm.nativeimage.c.function.CFunctionPointer;
import org.graalvm.nativeimage.hosted.Feature;
import com.oracle.svm.core.jni.JNIRuntimeAccess;
@@ -47,21 +46,27 @@
*
*
*
- * Native-to-Java call wrappers are generated as follows:
+ * Native-to-Java call wrappers are generated by {@link JNIAccessFeature} as follows:
*
- * - {@link JNIAccessFeature} creates a {@link JNIJavaCallWrapperMethod} for each method that is
- * callable from JNI, associates it with its corresponding {@link JNIAccessibleMethod}, and
- * registers it as an entry point. The method provides a graph that performs the Java method
- * invocation from native code and that is visible to the analysis.
- * - Because all {@link JNIJavaCallWrapperMethod call wrappers} are entry points, the call
- * wrappers and any code that is reachable through them is compiled.
- * - Before compilation, a {@link CFunctionPointer} is created for each call wrapper that is
- * eventually filled with the call wrapper's final entry point address.
- * - Looking up a Java method via JNI finds its {@link JNIAccessibleMethod}, reads its call
- * wrapper's entry address from the {@link CFunctionPointer}, and returns it as the jmethodID. The
- * JNI functions for calling methods, named
- * {@code Call[Static?][ReturnType]Method(env, obj, jmethodID)} only perform a jump to the provided
- * jmethodID argument, and the call wrapper code takes over execution.
+ * - A {@link JNICallTrampolineMethod} exists for each way how a Java method can be invoked
+ * (regular--including static--, and nonvirtual) and variant of passing arguments via JNI (varargs,
+ * array, va_list), and implements the JNI {@code Call{Static,Nonvirtual}?Method{V,A}?}
+ * functions, e.g. {@code CallIntMethod} or {@code CallNonvirtualObjectMethodA}. These trampolines
+ * dispatch to the {@link JNIJavaCallVariantWrapperMethod} corresponding to the call.
+ * - {@link JNIJavaCallVariantWrapperMethod}s are generated so that for each method which can be
+ * called via JNI and for each call variant, there exists a wrapper method which is compatible with
+ * the called method's signature. Each wrapper method extracts its arguments according to its call
+ * variant and passes them to a {@link JNIJavaCallWrapperMethod}.
+ * - A {@link JNIJavaCallWrapperMethod} for a specific signature resolves object handles in its
+ * arguments (if any) and calls the specific Java method either by dynamic dispatch via an object's
+ * vtable or via a function pointer for static or nonvirtual calls.
+ * Separating call-variant wrappers and call wrappers significantly reduces code size because
+ * call-variant wrappers can be made to be compatible to more different signatures than call
+ * wrappers could, and each target signature requires providing three to six (for nonvirtual calls)
+ * compatible call variant wrappers.
+ * - All dispatching is done via a {@code jmethodID}, which native code passes to the JNI call
+ * functions and which is actually a reference to a {@link JNIAccessibleMethod} object containing
+ * the function pointers for the wrapper methods and the target method and the vtable index.
*
*
*/
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIGraphKit.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIGraphKit.java
index e2ac81ed8792..c6e1bf337d7f 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIGraphKit.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIGraphKit.java
@@ -24,19 +24,39 @@
*/
package com.oracle.svm.jni.hosted;
+import org.graalvm.compiler.core.common.type.ObjectStamp;
+import org.graalvm.compiler.core.common.type.Stamp;
+import org.graalvm.compiler.core.common.type.StampFactory;
+import org.graalvm.compiler.core.common.type.TypeReference;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind;
+import org.graalvm.compiler.nodes.ConstantNode;
import org.graalvm.compiler.nodes.FixedWithNextNode;
import org.graalvm.compiler.nodes.InvokeWithExceptionNode;
+import org.graalvm.compiler.nodes.LogicNode;
+import org.graalvm.compiler.nodes.NodeView;
+import org.graalvm.compiler.nodes.PiNode;
import org.graalvm.compiler.nodes.ValueNode;
+import org.graalvm.compiler.nodes.calc.ConditionalNode;
+import org.graalvm.compiler.nodes.calc.IntegerEqualsNode;
+import org.graalvm.compiler.nodes.calc.NarrowNode;
+import org.graalvm.compiler.nodes.calc.SignExtendNode;
+import org.graalvm.compiler.nodes.calc.ZeroExtendNode;
+import org.graalvm.compiler.nodes.extended.BytecodeExceptionNode;
+import org.graalvm.compiler.nodes.extended.GuardingNode;
import org.graalvm.compiler.nodes.java.ExceptionObjectNode;
+import org.graalvm.compiler.nodes.java.InstanceOfNode;
import com.oracle.graal.pointsto.meta.HostedProviders;
import com.oracle.svm.hosted.phases.HostedGraphKit;
import com.oracle.svm.jni.JNIGeneratedMethodSupport;
+import com.oracle.svm.jni.access.JNIAccessibleMethod;
+import com.oracle.svm.jni.access.JNIReflectionDictionary;
+import com.oracle.svm.jni.nativeapi.JNIMethodId;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.ResolvedJavaMethod;
+import jdk.vm.ci.meta.ResolvedJavaType;
/**
* {@link HostedGraphKit} implementation with extensions that are specific to generated JNI code.
@@ -47,6 +67,49 @@ public class JNIGraphKit extends HostedGraphKit {
super(debug, providers, method);
}
+ public ValueNode checkObjectType(ValueNode uncheckedValue, ResolvedJavaType type, boolean checkNonNull) {
+ ValueNode value = uncheckedValue;
+ if (checkNonNull) {
+ value = maybeCreateExplicitNullCheck(value);
+ }
+ if (type.isJavaLangObject()) {
+ return value;
+ }
+ TypeReference typeRef = TypeReference.createTrusted(getAssumptions(), type);
+ LogicNode isInstance = InstanceOfNode.createAllowNull(typeRef, value, null, null);
+ if (!isInstance.isTautology()) {
+ append(isInstance);
+ ConstantNode expectedType = createConstant(getConstantReflection().asJavaClass(type), JavaKind.Object);
+ GuardingNode guard = createCheckThrowingBytecodeException(isInstance, false, BytecodeExceptionNode.BytecodeExceptionKind.CLASS_CAST, value, expectedType);
+ Stamp checkedStamp = value.stamp(NodeView.DEFAULT).improveWith(StampFactory.object(typeRef));
+ value = unique(new PiNode(value, checkedStamp, guard.asNode()));
+ }
+ return value;
+ }
+
+ /** Masks bits to ensure that unused bytes in the stack representation are cleared. */
+ public ValueNode maskNumericIntBytes(ValueNode value, JavaKind kind) {
+ assert kind.isNumericInteger();
+ int bits = kind.getByteCount() * Byte.SIZE;
+ ValueNode narrowed = append(NarrowNode.create(value, bits, NodeView.DEFAULT));
+ ValueNode widened = widenNumericInt(narrowed, kind);
+ if (kind == JavaKind.Boolean) {
+ LogicNode isZero = IntegerEqualsNode.create(widened, ConstantNode.forIntegerKind(kind.getStackKind(), 0), NodeView.DEFAULT);
+ widened = append(ConditionalNode.create(isZero, ConstantNode.forBoolean(false), ConstantNode.forBoolean(true), NodeView.DEFAULT));
+ }
+ return widened;
+ }
+
+ public ValueNode widenNumericInt(ValueNode value, JavaKind kind) {
+ assert kind.isNumericInteger();
+ int stackBits = kind.getStackKind().getBitCount();
+ if (kind.isUnsigned()) {
+ return append(ZeroExtendNode.create(value, stackBits, NodeView.DEFAULT));
+ } else {
+ return append(SignExtendNode.create(value, stackBits, NodeView.DEFAULT));
+ }
+ }
+
private InvokeWithExceptionNode createStaticInvoke(String name, ValueNode... args) {
return createInvokeWithExceptionAndUnwind(findMethod(JNIGeneratedMethodSupport.class, name, true), InvokeKind.Static, getFrameState(), bci(), args);
}
@@ -63,9 +126,7 @@ private FixedWithNextNode createStaticInvokeRetainException(String name, ValueNo
}
public InvokeWithExceptionNode nativeCallAddress(ValueNode linkage) {
- ResolvedJavaMethod method = findMethod(JNIGeneratedMethodSupport.class, "nativeCallAddress", true);
- int invokeBci = bci();
- return createInvokeWithExceptionAndUnwind(method, InvokeKind.Static, getFrameState(), invokeBci, linkage);
+ return createStaticInvoke("nativeCallAddress", linkage);
}
public InvokeWithExceptionNode nativeCallPrologue() {
@@ -92,6 +153,44 @@ public InvokeWithExceptionNode getFieldOffsetFromId(ValueNode fieldId) {
return createStaticInvoke("getFieldOffsetFromId", fieldId);
}
+ public InvokeWithExceptionNode getNewObjectAddress(ValueNode methodId) {
+ return invokeJNIMethodObjectMethod("getNewObjectAddress", methodId);
+ }
+
+ /** We trust our stored class object to be non-null. */
+ public ValueNode getDeclaringClassForMethod(ValueNode methodId) {
+ InvokeWithExceptionNode declaringClass = invokeJNIMethodObjectMethod("getDeclaringClassObject", methodId);
+ return createPiNode(declaringClass, ObjectStamp.pointerNonNull(declaringClass.stamp(NodeView.DEFAULT)));
+ }
+
+ public InvokeWithExceptionNode getJavaCallAddress(ValueNode methodId, ValueNode instance, ValueNode nonVirtual) {
+ return createInvokeWithExceptionAndUnwind(findMethod(JNIAccessibleMethod.class, "getJavaCallAddress", Object.class, boolean.class),
+ InvokeKind.Special, getFrameState(), bci(), getUncheckedMethodObject(methodId), instance, nonVirtual);
+ }
+
+ public InvokeWithExceptionNode getJavaCallWrapperAddressFromMethodId(ValueNode methodId) {
+ return invokeJNIMethodObjectMethod("getCallWrapperAddress", methodId);
+ }
+
+ public InvokeWithExceptionNode isStaticMethod(ValueNode methodId) {
+ return invokeJNIMethodObjectMethod("isStatic", methodId);
+ }
+
+ /**
+ * Used in native-to-Java call wrappers where the method ID has already been used to dispatch,
+ * and we would have crashed if something is wrong, so we can avoid null and type checks.
+ */
+ private PiNode getUncheckedMethodObject(ValueNode methodId) {
+ InvokeWithExceptionNode methodObj = createInvokeWithExceptionAndUnwind(
+ findMethod(JNIReflectionDictionary.class, "getObjectFromMethodID", JNIMethodId.class), InvokeKind.Static, getFrameState(), bci(), methodId);
+ ObjectStamp stamp = StampFactory.objectNonNull(TypeReference.createExactTrusted(getMetaAccess().lookupJavaType(JNIAccessibleMethod.class)));
+ return createPiNode(methodObj, stamp);
+ }
+
+ private InvokeWithExceptionNode invokeJNIMethodObjectMethod(String name, ValueNode methodId) {
+ return createInvokeWithExceptionAndUnwind(findMethod(JNIAccessibleMethod.class, name), InvokeKind.Special, getFrameState(), bci(), getUncheckedMethodObject(methodId));
+ }
+
public InvokeWithExceptionNode getStaticPrimitiveFieldsArray() {
return createStaticInvoke("getStaticPrimitiveFieldsArray");
}
@@ -109,9 +208,7 @@ public InvokeWithExceptionNode getAndClearPendingException() {
}
public InvokeWithExceptionNode rethrowPendingException() {
- ResolvedJavaMethod method = findMethod(JNIGeneratedMethodSupport.class, "rethrowPendingException", true);
- int invokeBci = bci();
- return createInvokeWithExceptionAndUnwind(method, InvokeKind.Static, getFrameState(), invokeBci);
+ return createStaticInvoke("rethrowPendingException");
}
public InvokeWithExceptionNode pinArrayAndGetAddress(ValueNode array, ValueNode isCopy) {
@@ -131,4 +228,8 @@ public FixedWithNextNode setPrimitiveArrayRegionRetainException(JavaKind element
assert elementKind.isPrimitive();
return createStaticInvokeRetainException("setPrimitiveArrayRegion", createObject(elementKind), array, start, count, buffer);
}
+
+ public ConstantNode createWord(long value) {
+ return ConstantNode.forIntegerKind(wordTypes.getWordKind(), value, graph);
+ }
}
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallVariantWrapperMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallVariantWrapperMethod.java
new file mode 100644
index 000000000000..42cf94b26ae2
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallVariantWrapperMethod.java
@@ -0,0 +1,288 @@
+/*
+ * 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.jni.hosted;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.graalvm.compiler.core.common.calc.FloatConvert;
+import org.graalvm.compiler.core.common.type.Stamp;
+import org.graalvm.compiler.core.common.type.StampFactory;
+import org.graalvm.compiler.core.common.type.StampPair;
+import org.graalvm.compiler.debug.DebugContext;
+import org.graalvm.compiler.nodes.AbstractMergeNode;
+import org.graalvm.compiler.nodes.CallTargetNode;
+import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind;
+import org.graalvm.compiler.nodes.ConstantNode;
+import org.graalvm.compiler.nodes.IndirectCallTargetNode;
+import org.graalvm.compiler.nodes.InvokeWithExceptionNode;
+import org.graalvm.compiler.nodes.NodeView;
+import org.graalvm.compiler.nodes.StructuredGraph;
+import org.graalvm.compiler.nodes.ValueNode;
+import org.graalvm.compiler.nodes.ValuePhiNode;
+import org.graalvm.compiler.nodes.calc.FloatConvertNode;
+import org.graalvm.compiler.nodes.memory.OnHeapMemoryAccess.BarrierType;
+import org.graalvm.compiler.nodes.memory.address.OffsetAddressNode;
+import org.graalvm.compiler.word.WordTypes;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.word.LocationIdentity;
+
+import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess;
+import com.oracle.graal.pointsto.infrastructure.WrappedJavaType;
+import com.oracle.graal.pointsto.infrastructure.WrappedSignature;
+import com.oracle.graal.pointsto.meta.AnalysisMetaAccess;
+import com.oracle.graal.pointsto.meta.HostedProviders;
+import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind;
+import com.oracle.svm.core.graal.nodes.CEntryPointEnterNode;
+import com.oracle.svm.core.graal.nodes.CEntryPointLeaveNode;
+import com.oracle.svm.core.graal.nodes.CInterfaceReadNode;
+import com.oracle.svm.core.graal.nodes.ReadCallerStackPointerNode;
+import com.oracle.svm.core.graal.nodes.VaListNextArgNode;
+import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.hosted.code.EntryPointCallStubMethod;
+import com.oracle.svm.hosted.code.SimpleSignature;
+import com.oracle.svm.hosted.meta.HostedMetaAccess;
+import com.oracle.svm.jni.JNIJavaCallVariantWrappers;
+
+import jdk.vm.ci.meta.JavaConstant;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.MetaAccessProvider;
+import jdk.vm.ci.meta.ResolvedJavaMethod;
+import jdk.vm.ci.meta.Signature;
+
+/**
+ * Generated code for taking arguments according to a specific signature and {@link CallVariant} and
+ * passing them on to a {@link JNIJavaCallWrapperMethod} which does the actual Java call. This
+ * method also enters the isolate and catches any exception.
+ */
+public class JNIJavaCallVariantWrapperMethod extends EntryPointCallStubMethod {
+ public enum CallVariant {
+ VARARGS,
+ ARRAY,
+ VA_LIST,
+ }
+
+ private final Signature callWrapperSignature;
+ private final CallVariant callVariant;
+ private final boolean nonVirtual;
+
+ public JNIJavaCallVariantWrapperMethod(SimpleSignature callWrapperSignature, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider originalMetaAccess, WordTypes wordTypes) {
+ super(createName(callWrapperSignature, callVariant, nonVirtual),
+ originalMetaAccess.lookupJavaType(JNIJavaCallVariantWrappers.class),
+ createSignature(callWrapperSignature, callVariant, nonVirtual, originalMetaAccess, wordTypes),
+ JNIJavaCallVariantWrappers.getConstantPool(originalMetaAccess));
+ this.callWrapperSignature = callWrapperSignature;
+ this.callVariant = callVariant;
+ this.nonVirtual = nonVirtual;
+ }
+
+ private static String createName(SimpleSignature targetSignature, CallVariant callVariant, boolean nonVirtual) {
+ return "invoke" + targetSignature.getIdentifier() + "_" + callVariant.name() + (nonVirtual ? "_Nonvirtual" : "");
+ }
+
+ private static Signature createSignature(Signature callWrapperSignature, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider originalMetaAccess, WordTypes wordTypes) {
+ JavaKind wordKind = wordTypes.getWordKind();
+ List args = new ArrayList<>();
+ args.add(wordKind); // JNIEnv
+ args.add(wordKind); // handle: this (instance method) or class (static method)
+ if (nonVirtual) {
+ args.add(wordKind); // handle: class of implementation to invoke
+ }
+ args.add(wordKind); // jmethodID
+ if (callVariant == CallVariant.VARARGS) {
+ int count = callWrapperSignature.getParameterCount(false);
+ for (int i = 3; i < count; i++) { // skip receiver/class, jmethodID, non-virtual args
+ JavaKind kind = callWrapperSignature.getParameterKind(i);
+ if (kind.isObject()) {
+ args.add(wordKind); // handle
+ } else if (kind == JavaKind.Float) {
+ // C varargs promote float to double (C99, 6.5.2.2-6)
+ args.add(JavaKind.Double);
+ } else { // C varargs promote sub-words to int (C99, 6.5.2.2-6)
+ args.add(kind.getStackKind());
+ }
+ }
+ } else if (callVariant == CallVariant.ARRAY) {
+ args.add(wordKind); // const jvalue *
+ } else if (callVariant == CallVariant.VA_LIST) {
+ args.add(wordKind); // va_list (a pointer of some kind)
+ } else {
+ throw VMError.shouldNotReachHere();
+ }
+ JavaKind returnType = callWrapperSignature.getReturnKind();
+ if (returnType.isObject()) {
+ returnType = wordKind; // handle
+ }
+ return SimpleSignature.fromKinds(args.toArray(JavaKind[]::new), returnType, originalMetaAccess);
+ }
+
+ @Override
+ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) {
+ UniverseMetaAccess metaAccess = (UniverseMetaAccess) providers.getMetaAccess();
+ JNIGraphKit kit = new JNIGraphKit(debug, providers, method);
+
+ AnalysisMetaAccess aMetaAccess = (AnalysisMetaAccess) ((metaAccess instanceof AnalysisMetaAccess) ? metaAccess : metaAccess.getWrapped());
+ Signature invokeSignature = aMetaAccess.getUniverse().lookup(callWrapperSignature, aMetaAccess.getUniverse().lookup(getDeclaringClass()));
+ if (metaAccess instanceof HostedMetaAccess) {
+ // signature might not exist in the hosted universe because it does not match any method
+ invokeSignature = new WrappedSignature(metaAccess.getUniverse(), invokeSignature, (WrappedJavaType) method.getDeclaringClass());
+ }
+
+ JavaKind wordKind = providers.getWordTypes().getWordKind();
+ int slotIndex = 0;
+ ValueNode env = kit.loadLocal(slotIndex, wordKind);
+ slotIndex += wordKind.getSlotCount();
+ ValueNode receiverOrClassHandle = kit.loadLocal(slotIndex, wordKind);
+ slotIndex += wordKind.getSlotCount();
+ if (nonVirtual) {
+ slotIndex += wordKind.getSlotCount();
+ }
+ ValueNode methodId = kit.loadLocal(slotIndex, wordKind);
+ slotIndex += wordKind.getSlotCount();
+
+ kit.append(CEntryPointEnterNode.enter(env));
+ ValueNode callAddress = kit.getJavaCallWrapperAddressFromMethodId(methodId);
+
+ List args = new ArrayList<>();
+ args.add(receiverOrClassHandle);
+ args.add(methodId);
+ args.add(kit.createInt(nonVirtual ? 1 : 0));
+ args.addAll(loadArguments(kit, providers, invokeSignature, args.size(), slotIndex));
+
+ ValueNode formerPendingException = kit.getAndClearPendingException();
+
+ StampPair returnStamp = StampFactory.forDeclaredType(kit.getAssumptions(), invokeSignature.getReturnType(null), false);
+ CallTargetNode callTarget = new IndirectCallTargetNode(callAddress, args.toArray(ValueNode[]::new), returnStamp, invokeSignature.toParameterTypes(null),
+ null, SubstrateCallingConventionKind.Java.toType(true), InvokeKind.Static);
+
+ int invokeBci = kit.bci();
+ InvokeWithExceptionNode invoke = kit.startInvokeWithException(callTarget, kit.getFrameState(), invokeBci);
+ kit.noExceptionPart();
+ kit.setPendingException(formerPendingException);
+ kit.exceptionPart();
+ kit.setPendingException(kit.exceptionObject());
+ AbstractMergeNode invokeMerge = kit.endInvokeWithException();
+
+ ValueNode returnValue = null;
+ JavaKind returnKind = JavaKind.Void;
+ if (invoke.getStackKind() == JavaKind.Void) {
+ invokeMerge.setStateAfter(kit.getFrameState().create(invokeBci, invokeMerge));
+ } else {
+ ValueNode exceptionValue = kit.unique(ConstantNode.defaultForKind(invoke.getStackKind()));
+ ValueNode[] inputs = {invoke, exceptionValue};
+ returnValue = kit.getGraph().addWithoutUnique(new ValuePhiNode(invoke.stamp(NodeView.DEFAULT), invokeMerge, inputs));
+ returnKind = returnValue.getStackKind();
+ kit.getFrameState().push(returnKind, returnValue);
+ invokeMerge.setStateAfter(kit.getFrameState().create(invokeBci, invokeMerge));
+ kit.getFrameState().pop(returnKind);
+ }
+
+ CEntryPointLeaveNode leave = new CEntryPointLeaveNode(CEntryPointLeaveNode.LeaveAction.Leave);
+ kit.append(leave);
+ kit.createReturn(returnValue, returnKind);
+ return kit.finalizeGraph();
+ }
+
+ /**
+ * Creates {@linkplain ValueNode IR nodes} for the arguments passed to the JNI call. The
+ * arguments do not include the receiver of the call, but only the actual arguments passed to
+ * the JNI target function.
+ *
+ * @return List of created argument nodes and their type
+ */
+ private List loadArguments(JNIGraphKit kit, HostedProviders providers, Signature invokeSignature, int firstParamIndex, int firstSlotIndex) {
+ JavaKind wordKind = providers.getWordTypes().getWordKind();
+ List args = new ArrayList<>();
+ int slotIndex = firstSlotIndex;
+ int count = invokeSignature.getParameterCount(false);
+ // Windows and iOS CallVariant.VA_LIST is identical to CallVariant.ARRAY
+ // iOS CallVariant.VARARGS stores values as an array on the stack
+ if (callVariant == CallVariant.ARRAY ||
+ (Platform.includedIn(Platform.DARWIN_AARCH64.class) && (callVariant == CallVariant.VARARGS || callVariant == CallVariant.VA_LIST)) ||
+ (Platform.includedIn(Platform.WINDOWS.class) && callVariant == CallVariant.VA_LIST)) {
+ ValueNode array;
+ if (callVariant == CallVariant.VARARGS) {
+ array = kit.append(new ReadCallerStackPointerNode());
+ } else {
+ array = kit.loadLocal(slotIndex, wordKind);
+ }
+ for (int i = firstParamIndex; i < count; i++) {
+ JavaKind kind = invokeSignature.getParameterKind(i);
+ assert kind == kind.getStackKind() : "sub-int conversions and bit masking must happen in JNIJavaCallWrapperMethod";
+ JavaKind readKind = kind;
+ if (kind == JavaKind.Float && (callVariant == CallVariant.VARARGS || callVariant == CallVariant.VA_LIST)) {
+ readKind = JavaKind.Double;
+ } else if (kind.isObject()) {
+ readKind = wordKind;
+ }
+ /*
+ * jvalue is a union, and all members of a C union should have the same address,
+ * which is that of the union itself (C99 6.7.2.1-14). Therefore, we do not need a
+ * field offset lookup and we can also read a larger type than we need (e.g. int for
+ * char) and mask the extra bits on little-endian architectures so that we can reuse
+ * a wrapper for more different signatures.
+ */
+ int offset = (i - firstParamIndex) * wordKind.getByteCount();
+ ConstantNode offsetConstant = kit.createConstant(JavaConstant.forIntegerKind(wordKind, offset), wordKind);
+ OffsetAddressNode address = kit.unique(new OffsetAddressNode(array, offsetConstant));
+ Stamp readStamp = StampFactory.forKind(readKind);
+ ValueNode value = kit.append(new CInterfaceReadNode(address, LocationIdentity.any(), readStamp, BarrierType.NONE, "args[" + i + "]"));
+ if (kind == JavaKind.Float && readKind == JavaKind.Double) {
+ value = kit.unique(new FloatConvertNode(FloatConvert.D2F, value));
+ }
+ args.add(value);
+ }
+ } else if (callVariant == CallVariant.VARARGS) {
+ for (int i = firstParamIndex; i < count; i++) {
+ JavaKind kind = invokeSignature.getParameterKind(i);
+ assert kind == kind.getStackKind() : "sub-int conversions and bit masking must happen in JNIJavaCallWrapperMethod";
+ JavaKind loadKind = kind;
+ if (loadKind == JavaKind.Float) {
+ loadKind = JavaKind.Double; // C varargs promote float to double (C99 6.5.2.2-6)
+ }
+ ValueNode value = kit.loadLocal(slotIndex, loadKind);
+ if (kind == JavaKind.Float) {
+ value = kit.unique(new FloatConvertNode(FloatConvert.D2F, value));
+ }
+ args.add(value);
+ slotIndex += loadKind.getSlotCount();
+ }
+ } else if (callVariant == CallVariant.VA_LIST) {
+ ValueNode valist = kit.loadLocal(slotIndex, wordKind);
+ for (int i = firstParamIndex; i < count; i++) {
+ JavaKind kind = invokeSignature.getParameterKind(i);
+ if (kind.isObject()) {
+ kind = wordKind;
+ }
+ assert kind == kind.getStackKind() : "sub-int conversions and bit masking must happen in JNIJavaCallWrapperMethod";
+ ValueNode value = kit.append(new VaListNextArgNode(kind, valist));
+ args.add(value);
+ }
+ } else {
+ throw VMError.unsupportedFeature("Call variant: " + callVariant);
+ }
+ return args;
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallWrapperMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallWrapperMethod.java
index 609687536e39..829b1914a0b8 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallWrapperMethod.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallWrapperMethod.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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
@@ -25,81 +25,47 @@
package com.oracle.svm.jni.hosted;
import java.lang.reflect.Constructor;
-import java.lang.reflect.Executable;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-import org.graalvm.collections.Pair;
-import org.graalvm.compiler.core.common.calc.FloatConvert;
import org.graalvm.compiler.core.common.type.Stamp;
import org.graalvm.compiler.core.common.type.StampFactory;
-import org.graalvm.compiler.core.common.type.TypeReference;
+import org.graalvm.compiler.core.common.type.StampPair;
import org.graalvm.compiler.debug.DebugContext;
-import org.graalvm.compiler.java.FrameStateBuilder;
import org.graalvm.compiler.nodes.AbstractMergeNode;
-import org.graalvm.compiler.nodes.BeginNode;
-import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind;
+import org.graalvm.compiler.nodes.CallTargetNode;
import org.graalvm.compiler.nodes.ConstantNode;
-import org.graalvm.compiler.nodes.EndNode;
-import org.graalvm.compiler.nodes.IfNode;
+import org.graalvm.compiler.nodes.IndirectCallTargetNode;
import org.graalvm.compiler.nodes.InvokeWithExceptionNode;
import org.graalvm.compiler.nodes.LogicNode;
-import org.graalvm.compiler.nodes.MergeNode;
import org.graalvm.compiler.nodes.NodeView;
-import org.graalvm.compiler.nodes.PiNode;
+import org.graalvm.compiler.nodes.ProfileData.BranchProbabilityData;
import org.graalvm.compiler.nodes.StructuredGraph;
+import org.graalvm.compiler.nodes.UnwindNode;
import org.graalvm.compiler.nodes.ValueNode;
import org.graalvm.compiler.nodes.ValuePhiNode;
-import org.graalvm.compiler.nodes.calc.ConditionalNode;
-import org.graalvm.compiler.nodes.calc.FloatConvertNode;
import org.graalvm.compiler.nodes.calc.IntegerEqualsNode;
-import org.graalvm.compiler.nodes.calc.IsNullNode;
-import org.graalvm.compiler.nodes.calc.NarrowNode;
import org.graalvm.compiler.nodes.calc.ObjectEqualsNode;
-import org.graalvm.compiler.nodes.calc.SignExtendNode;
-import org.graalvm.compiler.nodes.calc.ZeroExtendNode;
import org.graalvm.compiler.nodes.extended.BranchProbabilityNode;
-import org.graalvm.compiler.nodes.extended.BytecodeExceptionNode;
-import org.graalvm.compiler.nodes.extended.BytecodeExceptionNode.BytecodeExceptionKind;
import org.graalvm.compiler.nodes.java.ExceptionObjectNode;
-import org.graalvm.compiler.nodes.java.InstanceOfNode;
-import org.graalvm.compiler.nodes.memory.OnHeapMemoryAccess.BarrierType;
-import org.graalvm.compiler.nodes.memory.address.OffsetAddressNode;
+import org.graalvm.compiler.nodes.java.InstanceOfDynamicNode;
import org.graalvm.compiler.nodes.type.StampTool;
-import org.graalvm.nativeimage.Platform;
-import org.graalvm.nativeimage.c.struct.SizeOf;
-import org.graalvm.word.LocationIdentity;
-import org.graalvm.word.WordBase;
+import org.graalvm.compiler.word.WordTypes;
import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess;
+import com.oracle.graal.pointsto.infrastructure.WrappedJavaType;
+import com.oracle.graal.pointsto.infrastructure.WrappedSignature;
+import com.oracle.graal.pointsto.meta.AnalysisMetaAccess;
import com.oracle.graal.pointsto.meta.HostedProviders;
-import com.oracle.svm.core.SubstrateUtil;
-import com.oracle.svm.core.graal.nodes.CEntryPointEnterNode;
-import com.oracle.svm.core.graal.nodes.CEntryPointLeaveNode;
-import com.oracle.svm.core.graal.nodes.CEntryPointLeaveNode.LeaveAction;
-import com.oracle.svm.core.graal.nodes.CInterfaceReadNode;
+import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind;
import com.oracle.svm.core.graal.nodes.LoweredDeadEndNode;
-import com.oracle.svm.core.graal.nodes.ReadCallerStackPointerNode;
-import com.oracle.svm.core.graal.nodes.VaListNextArgNode;
-import com.oracle.svm.core.util.VMError;
-import com.oracle.svm.hosted.c.NativeLibraries;
-import com.oracle.svm.hosted.c.info.ElementInfo;
-import com.oracle.svm.hosted.c.info.StructFieldInfo;
-import com.oracle.svm.hosted.c.info.StructInfo;
-import com.oracle.svm.hosted.code.EntryPointCallStubMethod;
import com.oracle.svm.hosted.code.FactoryMethodSupport;
+import com.oracle.svm.hosted.code.NonBytecodeStaticMethod;
import com.oracle.svm.hosted.code.SimpleSignature;
+import com.oracle.svm.hosted.meta.HostedMetaAccess;
import com.oracle.svm.jni.JNIJavaCallWrappers;
-import com.oracle.svm.jni.nativeapi.JNIEnvironment;
-import com.oracle.svm.jni.nativeapi.JNIMethodId;
-import com.oracle.svm.jni.nativeapi.JNIObjectHandle;
-import com.oracle.svm.jni.nativeapi.JNIValue;
+import com.oracle.svm.jni.access.JNIAccessibleMethod;
import com.oracle.svm.util.ReflectionUtil;
-import jdk.vm.ci.meta.Constant;
-import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.JavaType;
import jdk.vm.ci.meta.MetaAccessProvider;
@@ -108,10 +74,10 @@
import jdk.vm.ci.meta.Signature;
/**
- * Generated code for calling a specific Java method from native code. The wrapper takes care of
- * transitioning to a Java context and back to native code, for catching and retaining unhandled
- * exceptions, and if required, for unboxing object handle arguments and boxing an object return
- * value.
+ * Generated code with a specific signature for calling a Java method that has a compatible
+ * signature from native code. The wrapper takes care of transitioning to a Java context and back to
+ * native code, for catching and retaining unhandled exceptions, and if required, for unboxing
+ * object handle arguments and boxing an object return value.
*
* @see Java 8 JNI
@@ -119,473 +85,256 @@
* @see Java 11
* JNI functions documentation
*/
-public class JNIJavaCallWrapperMethod extends EntryPointCallStubMethod {
+public class JNIJavaCallWrapperMethod extends NonBytecodeStaticMethod {
+ private static final Constructor CLASS_CAST_EXCEPTION_CONSTRUCTOR = ReflectionUtil.lookupConstructor(ClassCastException.class);
+ private static final Constructor INSTANTIATION_EXCEPTION_CONSTRUCTOR = ReflectionUtil.lookupConstructor(InstantiationException.class);
+
public static class Factory {
- public JNIJavaCallWrapperMethod create(Executable reflectMethod, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider metaAccess, NativeLibraries nativeLibs) {
- return new JNIJavaCallWrapperMethod(reflectMethod, callVariant, nonVirtual, metaAccess, nativeLibs);
+ public JNIJavaCallWrapperMethod create(SimpleSignature targetSignature, MetaAccessProvider originalMetaAccess, WordTypes wordTypes) {
+ return new JNIJavaCallWrapperMethod(targetSignature, originalMetaAccess, wordTypes);
}
- }
- public enum CallVariant {
- VARARGS,
- ARRAY,
- VA_LIST,
+ @SuppressWarnings("unused")
+ public boolean canInvokeConstructorOnObject(ResolvedJavaMethod constructor, MetaAccessProvider originalMetaAccess) {
+ return true;
+ }
}
- private final NativeLibraries nativeLibs;
-
- private final Executable reflectMethod;
- private final CallVariant callVariant;
- private final boolean nonVirtual;
-
- protected JNIJavaCallWrapperMethod(Executable reflectMethod, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider metaAccess, NativeLibraries nativeLibs) {
- super(createName(reflectMethod, callVariant, nonVirtual),
- metaAccess.lookupJavaType(JNIJavaCallWrappers.class),
- createSignature(reflectMethod, callVariant, nonVirtual, metaAccess),
- JNIJavaCallWrappers.getConstantPool(metaAccess));
- assert !nonVirtual || !Modifier.isStatic(reflectMethod.getModifiers());
- this.reflectMethod = reflectMethod;
- this.nativeLibs = nativeLibs;
- this.callVariant = callVariant;
- this.nonVirtual = nonVirtual;
+ public static SimpleSignature getGeneralizedSignatureForTarget(ResolvedJavaMethod targetMethod, MetaAccessProvider originalMetaAccess) {
+ JavaType[] paramTypes = targetMethod.getSignature().toParameterTypes(null);
+ // Note: our parameters do not include the receiver, but we can do a type check based on the
+ // JNIAccessibleMethod object we get from the method id.
+ JavaKind returnKind = targetMethod.getSignature().getReturnKind();
+ if (targetMethod.isConstructor()) {
+ returnKind = JavaKind.Object; // return new (or previously allocated) object
+ } else if (returnKind.isNumericInteger() || returnKind == JavaKind.Void) {
+ // Use long for void and integer return types to increase the reusability of call
+ // wrappers. This is fine with our supported 64-bit calling conventions.
+ returnKind = JavaKind.Long;
+ }
+ // Note: no need to distinguish between object return types for us, the return value must
+ // match in Java code and we return it as handle anyway.
+ JavaType returnType = originalMetaAccess.lookupJavaType(returnKind.isObject() ? Object.class : returnKind.toJavaClass());
+ return new SimpleSignature(paramTypes, returnType);
}
- private static String createName(Executable reflectMethod, CallVariant callVariant, boolean nonVirtual) {
- return "jniInvoke_" + callVariant.name() + (nonVirtual ? "_Nonvirtual" : "") + "_" + SubstrateUtil.uniqueShortName(reflectMethod);
+ private final Signature targetSignature;
+
+ protected JNIJavaCallWrapperMethod(SimpleSignature targetSignature, MetaAccessProvider metaAccess, WordTypes wordTypes) {
+ super("invoke_" + targetSignature.getIdentifier(), metaAccess.lookupJavaType(JNIJavaCallWrappers.class),
+ createSignature(targetSignature, metaAccess, wordTypes), JNIJavaCallWrappers.getConstantPool(metaAccess));
+ this.targetSignature = targetSignature;
}
- private static SimpleSignature createSignature(Executable reflectMethod, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider metaAccess) {
- ResolvedJavaType objectHandle = metaAccess.lookupJavaType(JNIObjectHandle.class);
- List args = new ArrayList<>();
- args.add(metaAccess.lookupJavaType(JNIEnvironment.class));
- args.add(objectHandle); // this (instance method) or class (static method)
- if (nonVirtual) {
- args.add(objectHandle); // class of implementation to invoke
- }
- args.add(metaAccess.lookupJavaType(JNIMethodId.class));
- ResolvedJavaMethod targetMethod = metaAccess.lookupJavaMethod(reflectMethod);
- Signature targetSignature = targetMethod.getSignature();
- if (callVariant == CallVariant.VARARGS) {
- for (JavaType targetArg : targetSignature.toParameterTypes(null)) {
- JavaKind kind = targetArg.getJavaKind();
- if (kind.isObject()) {
- args.add(objectHandle);
- } else if (kind == JavaKind.Float) { // C varargs promote float to double
- args.add(metaAccess.lookupJavaType(JavaKind.Double.toJavaClass()));
- } else { // C varargs promote sub-words to int
- args.add(metaAccess.lookupJavaType(kind.getStackKind().toJavaClass()));
- }
+ private static SimpleSignature createSignature(Signature targetSignature, MetaAccessProvider originalMetaAccess, WordTypes wordTypes) {
+ JavaKind wordKind = wordTypes.getWordKind();
+ int count = targetSignature.getParameterCount(false);
+ JavaKind[] args = new JavaKind[3 + count];
+ args[0] = wordKind; // this (instance method) or class (static method) handle
+ args[1] = wordKind; // jmethodID
+ args[2] = JavaKind.Boolean.getStackKind(); // non-virtual?
+ for (int i = 0; i < count; i++) { // skip non-virtual, receiver/class arguments
+ JavaKind kind = targetSignature.getParameterKind(i);
+ if (kind.isObject()) {
+ kind = wordKind; // handle
}
- } else if (callVariant == CallVariant.ARRAY) {
- args.add(metaAccess.lookupJavaType(JNIValue.class)); // const jvalue *
- } else if (callVariant == CallVariant.VA_LIST) {
- args.add(metaAccess.lookupJavaType(WordBase.class)); // va_list (a pointer of some kind)
- } else {
- throw VMError.shouldNotReachHere();
+ /*
+ * Widen to the stack kind, i.e. from boolean/byte/short/char to int, for greater
+ * reusability of call variant wrappers. This also changes the parameter type from C,
+ * but that is not an issue: C requires an equivalent integer promotion to take place
+ * for vararg calls (C99, 6.5.2.2-6), which also applies to JNI calls taking a va_list.
+ * For JNI calls which are passed parameters in jvalue arrays, we can just mask the
+ * extra bits in this method thanks to little endian order.
+ */
+ args[3 + i] = kind.getStackKind();
}
- JavaType returnType = targetSignature.getReturnType(null);
- if (returnType.getJavaKind().isObject() || targetMethod.isConstructor()) {
- // Constructor: returns `this` to implement NewObject
- returnType = objectHandle;
+ JavaKind returnKind = targetSignature.getReturnKind();
+ if (returnKind.isObject()) {
+ returnKind = wordKind; // handle
}
- return new SimpleSignature(args, returnType);
+ return SimpleSignature.fromKinds(args, returnKind, originalMetaAccess);
+ }
+
+ @Override
+ public SimpleSignature getSignature() {
+ return (SimpleSignature) super.getSignature();
}
@Override
public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) {
UniverseMetaAccess metaAccess = (UniverseMetaAccess) providers.getMetaAccess();
JNIGraphKit kit = new JNIGraphKit(debug, providers, method);
- FrameStateBuilder state = new FrameStateBuilder(null, method, kit.getGraph());
- state.initializeForMethodStart(null, true, providers.getGraphBuilderPlugins());
-
- JavaKind vmThreadKind = metaAccess.lookupJavaType(JNIEnvironment.class).getJavaKind();
- ValueNode vmThread = kit.loadLocal(0, vmThreadKind);
- kit.append(CEntryPointEnterNode.enter(vmThread));
-
- ResolvedJavaMethod invokeMethod = providers.getMetaAccess().lookupJavaMethod(reflectMethod);
- Signature invokeSignature = invokeMethod.getSignature();
- List> argsWithTypes = loadAndUnboxArguments(kit, providers, invokeSignature);
-
- /* Unbox receiver handle if there is one. */
- ValueNode unboxedReceiver = null;
- if (invokeMethod.hasReceiver()) {
- int javaIndex = metaAccess.lookupJavaType(JNIEnvironment.class).getJavaKind().getSlotCount();
- JavaKind handleKind = metaAccess.lookupJavaType(JNIObjectHandle.class).getJavaKind();
- ValueNode handle = kit.loadLocal(javaIndex, handleKind);
- unboxedReceiver = kit.unboxHandle(handle);
- }
- /*
- * Dynamically type-check the call arguments. Use a chain of IfNodes rather than a logic
- * expression that can become too complex for the static analysis.
- */
- List illegalTypeEnds = new ArrayList<>();
- int argIndex = invokeMethod.hasReceiver() ? 1 : 0;
- ValueNode[] args = new ValueNode[argIndex + argsWithTypes.size()];
- for (Pair argsWithType : argsWithTypes) {
- ValueNode value = argsWithType.getLeft();
- ResolvedJavaType type = argsWithType.getRight();
- if (!type.isPrimitive() && !type.isJavaLangObject()) {
- value = typeChecked(kit, value, type, illegalTypeEnds, false);
- }
- args[argIndex++] = value;
+ AnalysisMetaAccess aMetaAccess = (AnalysisMetaAccess) ((metaAccess instanceof AnalysisMetaAccess) ? metaAccess : metaAccess.getWrapped());
+ Signature invokeSignature = aMetaAccess.getUniverse().lookup(targetSignature, aMetaAccess.getUniverse().lookup(getDeclaringClass()));
+ if (metaAccess instanceof HostedMetaAccess) {
+ // signature might not exist in the hosted universe because it does not match any method
+ invokeSignature = new WrappedSignature(metaAccess.getUniverse(), invokeSignature, (WrappedJavaType) method.getDeclaringClass());
}
- /* Dynamically type-check the receiver type, and invoke the method if it matches. */
- InvokeKind invokeKind = invokeMethod.isStatic() ? InvokeKind.Static : //
- ((nonVirtual || invokeMethod.isConstructor()) ? InvokeKind.Special : InvokeKind.Virtual);
- ValueNode returnValue;
- if (!invokeMethod.hasReceiver()) {
- returnValue = createMethodCall(kit, invokeMethod, invokeKind, state, args);
- } else if (invokeMethod.isConstructor()) {
- /*
- * If the target method is a constructor, we can narrow down the JNI call to two
- * possible types of JNI functions: `CallMethod` or `NewObject`.
- *
- * To distinguish `CallMethod` from `NewObject`, we can look at JNI call parameter
- * 1, which is either `jobject obj` (the receiver object) in the case of
- * `CallMethod`, or `jclass clazz` (the hub of the receiver object) in the case of
- * `NewObject`.
- */
- ResolvedJavaType receiverClass = invokeMethod.getDeclaringClass();
- Constant hub = providers.getConstantReflection().asObjectHub(receiverClass);
- ConstantNode hubNode = kit.createConstant(hub, JavaKind.Object);
- ObjectEqualsNode isNewObjectCall = kit.unique(new ObjectEqualsNode(unboxedReceiver, hubNode));
- kit.startIf(isNewObjectCall, BranchProbabilityNode.FAST_PATH_PROFILE);
- kit.thenPart();
- ValueNode createdObjectOrNull;
- if (invokeMethod.getDeclaringClass().isAbstract()) {
- createdObjectOrNull = throwInstantiationException(metaAccess, kit, state);
- } else {
- createdObjectOrNull = createNewObjectCall(metaAccess, kit, invokeMethod, state, args);
- }
-
- kit.elsePart();
- args[0] = typeChecked(kit, unboxedReceiver, invokeMethod.getDeclaringClass(), illegalTypeEnds, true);
- ValueNode unboxedReceiverOrNull = createMethodCall(kit, invokeMethod, invokeKind, state, args);
-
- AbstractMergeNode merge = kit.endIf();
- merge.setStateAfter(kit.getFrameState().create(kit.bci(), merge));
- returnValue = kit.unique(new ValuePhiNode(StampFactory.object(), merge, new ValueNode[]{createdObjectOrNull, unboxedReceiverOrNull}));
- } else {
- // This is a JNI call via `CallMethod` to a non-static method
- args[0] = typeChecked(kit, unboxedReceiver, invokeMethod.getDeclaringClass(), illegalTypeEnds, true);
- returnValue = createMethodCall(kit, invokeMethod, invokeKind, state, args);
- }
- JavaKind returnKind = (returnValue != null) ? returnValue.getStackKind() : JavaKind.Void;
+ JavaKind wordKind = providers.getWordTypes().getWordKind();
+ int slotIndex = 0;
+ ValueNode receiverOrClassHandle = kit.loadLocal(slotIndex, wordKind);
+ ValueNode receiverOrClass = kit.unboxHandle(receiverOrClassHandle);
+ slotIndex += wordKind.getSlotCount();
+ ValueNode methodId = kit.loadLocal(slotIndex, wordKind);
+ slotIndex += wordKind.getSlotCount();
+ ValueNode nonVirtual = kit.loadLocal(slotIndex, JavaKind.Boolean.getStackKind());
+ slotIndex += JavaKind.Boolean.getStackKind().getSlotCount();
- if (!illegalTypeEnds.isEmpty()) {
- /*
- * The following is awkward because we need to maintain a last fixed node in GraphKit
- * while building non-sequential control flow, so we append nodes and rewire control
- * flow later. Be careful when making any changes.
- */
- BeginNode afterSuccess = kit.append(new BeginNode());
-
- ValueNode exception;
- if (illegalTypeEnds.size() == 1) {
- BeginNode illegalTypeBegin = kit.append(new BeginNode());
- illegalTypeBegin.replaceAtPredecessor(null);
-
- EndNode end = illegalTypeEnds.get(0);
- exception = (BytecodeExceptionNode) end.predecessor();
- end.replaceAtPredecessor(illegalTypeBegin);
- end.safeDelete();
- } else {
- MergeNode illegalTypesMerge = kit.append(new MergeNode());
- ValuePhiNode phi = kit.getGraph().addWithoutUnique(new ValuePhiNode(StampFactory.object(), illegalTypesMerge));
- for (EndNode end : illegalTypeEnds) {
- illegalTypesMerge.addForwardEnd(end);
- phi.addInput((BytecodeExceptionNode) end.predecessor());
- }
- illegalTypesMerge.setStateAfter(state.create(kit.bci(), illegalTypesMerge));
- phi.inferStamp();
- exception = phi;
- }
- kit.setPendingException(exception);
- BeginNode afterIllegalType = kit.append(new BeginNode());
-
- MergeNode returnMerge = kit.append(new MergeNode());
- EndNode afterSuccessEnd = kit.add(new EndNode());
- afterSuccess.setNext(afterSuccessEnd);
- returnMerge.addForwardEnd(afterSuccessEnd);
- EndNode afterIllegalTypeEnd = kit.add(new EndNode());
- afterIllegalType.setNext(afterIllegalTypeEnd);
- returnMerge.addForwardEnd(afterIllegalTypeEnd);
-
- if (returnValue != null) {
- // Create Phi for the return value, with null/zero/false on the exception branch.
- ValueNode typeMismatchResult = kit.unique(ConstantNode.defaultForKind(returnValue.getStackKind()));
- ValueNode[] inputs = {returnValue, typeMismatchResult};
- returnValue = kit.getGraph().addWithoutUnique(new ValuePhiNode(returnValue.stamp(NodeView.DEFAULT), returnMerge, inputs));
- state.push(returnKind, returnValue);
- returnMerge.setStateAfter(state.create(kit.bci(), returnMerge));
- state.pop(returnKind);
- } else {
- returnMerge.setStateAfter(state.create(kit.bci(), returnMerge));
- }
- kit.appendStateSplitProxy(state);
- }
+ ValueNode[] args = loadAndUnboxArguments(kit, providers, invokeSignature, slotIndex);
+ ValueNode returnValue = createCall(kit, invokeSignature, methodId, receiverOrClass, nonVirtual, args);
+ JavaKind returnKind = returnValue.getStackKind();
if (returnKind.isObject()) {
returnValue = kit.boxObjectInLocalHandle(returnValue);
}
-
- CEntryPointLeaveNode leave = new CEntryPointLeaveNode(LeaveAction.Leave);
- kit.append(leave);
kit.createReturn(returnValue, returnKind);
-
return kit.finalizeGraph();
}
- /**
- * Builds the object allocation for a JNI {@code NewObject} call, returning a node that contains
- * the created object or for {@code null} when an exception occurred (in which case the
- * exception becomes a JNI pending exception).
- */
- private static ValueNode createNewObjectCall(UniverseMetaAccess metaAccess, JNIGraphKit kit, ResolvedJavaMethod constructor, FrameStateBuilder state, ValueNode... argsWithReceiver) {
- assert constructor.isConstructor() : "Cannot create a NewObject call to the non-constructor method " + constructor;
-
- ResolvedJavaMethod factoryMethod = FactoryMethodSupport.singleton().lookup(metaAccess, constructor, false);
-
- int bci = kit.bci();
- ValueNode[] argsWithoutReceiver = Arrays.copyOfRange(argsWithReceiver, 1, argsWithReceiver.length);
- ValueNode createdObject = startInvokeWithRetainedException(kit, factoryMethod, InvokeKind.Static, state, bci, argsWithoutReceiver);
- AbstractMergeNode merge = kit.endInvokeWithException();
- merge.setStateAfter(state.create(bci, merge));
-
- Stamp objectStamp = StampFactory.forDeclaredType(null, constructor.getDeclaringClass(), true).getTrustedStamp();
- ValueNode exceptionValue = kit.unique(ConstantNode.defaultForKind(JavaKind.Object));
- return kit.getGraph().addWithoutUnique(new ValuePhiNode(objectStamp, merge, new ValueNode[]{createdObject, exceptionValue}));
- }
+ private ValueNode createCall(JNIGraphKit kit, Signature invokeSignature, ValueNode methodId, ValueNode receiverOrClass, ValueNode nonVirtual, ValueNode[] args) {
+ ValueNode declaringClass = kit.getDeclaringClassForMethod(methodId);
+ if (!invokeSignature.getReturnKind().isObject()) {
+ return createRegularMethodCall(kit, invokeSignature, methodId, declaringClass, receiverOrClass, nonVirtual, args);
+ }
- private static final Constructor INSTANTIATION_EXCEPTION_CONSTRUCTOR = ReflectionUtil.lookupConstructor(InstantiationException.class);
+ ValueNode newObjectAddress = kit.getNewObjectAddress(methodId);
+ kit.startIf(IntegerEqualsNode.create(newObjectAddress, kit.createWord(0), NodeView.DEFAULT), BranchProbabilityData.unknown());
+ kit.thenPart();
+ ValueNode methodReturnValue = createRegularMethodCall(kit, invokeSignature, methodId, declaringClass, receiverOrClass, nonVirtual, args);
+ kit.elsePart();
+ ValueNode receiverOrCreatedObject = createNewObjectOrConstructorCall(kit, invokeSignature, methodId, declaringClass, newObjectAddress, receiverOrClass, args);
+ AbstractMergeNode merge = kit.endIf();
+ return mergeValues(kit, merge, kit.bci(), methodReturnValue, receiverOrCreatedObject);
+ }
- /**
- * When trying to allocate an abstract class, allocate and throw exception instead. The
- * exception is installed as the JNI pending exception, and the null constant is returned.
- */
- private static ValueNode throwInstantiationException(UniverseMetaAccess metaAccess, JNIGraphKit kit, FrameStateBuilder state) {
- ResolvedJavaMethod throwMethod = FactoryMethodSupport.singleton().lookup(metaAccess, metaAccess.lookupJavaMethod(INSTANTIATION_EXCEPTION_CONSTRUCTOR), true);
- int bci = kit.bci();
- kit.startInvokeWithException(throwMethod, InvokeKind.Static, state, bci);
- kit.noExceptionPart();
- kit.append(new LoweredDeadEndNode());
- kit.exceptionPart();
- kit.setPendingException(kit.exceptionObject());
- kit.endInvokeWithException();
- return kit.unique(ConstantNode.defaultForKind(JavaKind.Object));
+ private static ValueNode createRegularMethodCall(JNIGraphKit kit, Signature invokeSignature, ValueNode methodId,
+ ValueNode declaringClass, ValueNode receiverOrClass, ValueNode nonVirtual, ValueNode[] args) {
+ ValueNode methodAddress = kit.getJavaCallAddress(methodId, receiverOrClass, nonVirtual);
+ ValueNode isStatic = kit.isStaticMethod(methodId);
+ kit.startIf(IntegerEqualsNode.create(isStatic, kit.createInt(0), NodeView.DEFAULT), BranchProbabilityData.unknown());
+ kit.thenPart();
+ ValueNode nonstaticResult = createMethodCallWithReceiver(kit, invokeSignature, declaringClass, methodAddress, receiverOrClass, args);
+ kit.elsePart();
+ ValueNode staticResult = createMethodCall(kit, invokeSignature.getReturnType(null), invokeSignature.toParameterTypes(null), methodAddress, args);
+ AbstractMergeNode merge = kit.endIf();
+ return mergeValues(kit, merge, kit.bci(), nonstaticResult, staticResult);
}
- /**
- * Builds a JNI {@code CallMethod} call, returning a node that contains the return value
- * or null/zero/false when an exception occurred (in which case the exception becomes a JNI
- * pending exception).
- */
- protected ValueNode createMethodCall(JNIGraphKit kit, ResolvedJavaMethod invokeMethod, InvokeKind invokeKind, FrameStateBuilder state, ValueNode... args) {
- int bci = kit.bci();
- InvokeWithExceptionNode invoke = startInvokeWithRetainedException(kit, invokeMethod, invokeKind, state, bci, args);
- AbstractMergeNode invokeMerge = kit.endInvokeWithException();
-
- if (invoke.getStackKind() == JavaKind.Void && !invokeMethod.isConstructor()) {
- invokeMerge.setStateAfter(state.create(bci, invokeMerge));
- return null;
- }
+ protected ValueNode createNewObjectOrConstructorCall(JNIGraphKit kit, Signature invokeSignature, ValueNode methodId,
+ ValueNode declaringClass, ValueNode newObjectAddress, ValueNode receiverOrClass, ValueNode[] args) {
+ /*
+ * The called function could either be NewObject or CallMethod with a constructor
+ * (without creating a new object).
+ *
+ * To distinguish them, we look at the second parameter, which is either `jobject obj` (the
+ * receiver object) for `CallMethod`, or `jclass clazz` (hub of the receiver object)
+ * for `NewObject`.
+ */
+ ObjectEqualsNode isNewObjectCall = kit.unique(new ObjectEqualsNode(receiverOrClass, declaringClass));
+ kit.startIf(isNewObjectCall, BranchProbabilityNode.FAST_PATH_PROFILE);
+ kit.thenPart();
+ ValueNode createdObject = createNewObjectCall(kit, invokeSignature, newObjectAddress, args);
- ValueNode successValue = invokeMethod.isConstructor() ? args[0] : invoke;
- ValueNode exceptionValue = kit.unique(ConstantNode.defaultForKind(successValue.getStackKind()));
- ValueNode[] inputs = {successValue, exceptionValue};
- ValueNode returnValue = kit.getGraph().addWithoutUnique(new ValuePhiNode(successValue.stamp(NodeView.DEFAULT), invokeMerge, inputs));
- JavaKind returnKind = returnValue.getStackKind();
- state.push(returnKind, returnValue);
- invokeMerge.setStateAfter(state.create(bci, invokeMerge));
- state.pop(returnKind);
- return returnValue;
+ kit.elsePart();
+ createConstructorCall(kit, invokeSignature, methodId, declaringClass, receiverOrClass, args);
+
+ AbstractMergeNode merge = kit.endIf();
+ return mergeValues(kit, merge, kit.bci(), createdObject, receiverOrClass);
}
- protected static InvokeWithExceptionNode startInvokeWithRetainedException(JNIGraphKit kit, ResolvedJavaMethod invokeMethod, InvokeKind kind, FrameStateBuilder state, int bci, ValueNode... args) {
- ValueNode formerPendingException = kit.getAndClearPendingException();
- InvokeWithExceptionNode invoke = kit.startInvokeWithException(invokeMethod, kind, state, bci, args);
+ protected ValueNode createConstructorCall(JNIGraphKit kit, Signature invokeSignature, ValueNode methodId, ValueNode declaringClass, ValueNode receiverOrClass, ValueNode[] args) {
+ ValueNode methodAddress = kit.getJavaCallAddress(methodId, receiverOrClass, kit.createInt(1));
+ return createMethodCallWithReceiver(kit, invokeSignature, declaringClass, methodAddress, receiverOrClass, args);
+ }
- kit.noExceptionPart(); // no new exception was thrown, restore the formerly pending one
- kit.setPendingException(formerPendingException);
+ private static ValueNode createMethodCallWithReceiver(JNIGraphKit kit, Signature invokeSignature, ValueNode declaringClass, ValueNode methodAddress, ValueNode receiver, ValueNode[] args) {
+ dynamicTypeCheckReceiver(kit, declaringClass, receiver);
- kit.exceptionPart();
- ExceptionObjectNode exceptionObject = kit.exceptionObject();
- kit.setPendingException(exceptionObject);
-
- return invoke;
+ ValueNode[] argsWithReceiver = new ValueNode[1 + args.length];
+ argsWithReceiver[0] = receiver;
+ System.arraycopy(args, 0, argsWithReceiver, 1, args.length);
+ JavaType[] paramTypes = invokeSignature.toParameterTypes(kit.getMetaAccess().lookupJavaType(Object.class));
+ return createMethodCall(kit, invokeSignature.getReturnType(null), paramTypes, methodAddress, argsWithReceiver);
}
- private static PiNode typeChecked(JNIGraphKit kit, ValueNode uncheckedValue, ResolvedJavaType type, List illegalTypeEnds, boolean isReceiver) {
- ValueNode value = uncheckedValue;
- if (isReceiver && !StampTool.isPointerNonNull(value)) {
- IfNode ifNode = kit.startIf(kit.unique(IsNullNode.create(value)), BranchProbabilityNode.SLOW_PATH_PROFILE);
- kit.thenPart();
- kit.append(kit.createBytecodeExceptionObjectNode(BytecodeExceptionKind.NULL_POINTER, false));
- illegalTypeEnds.add(kit.append(new EndNode()));
- kit.endIf();
- Stamp nonNullStamp = value.stamp(NodeView.DEFAULT).improveWith(StampFactory.objectNonNull());
- value = kit.append(new PiNode(value, nonNullStamp, ifNode.falseSuccessor()));
- }
- TypeReference typeRef = TypeReference.createTrusted(kit.getAssumptions(), type);
- LogicNode instanceOf = kit.append(InstanceOfNode.createAllowNull(typeRef, value, null, null));
- IfNode ifNode = kit.startIf(instanceOf, BranchProbabilityNode.FAST_PATH_PROFILE);
+ private static void dynamicTypeCheckReceiver(JNIGraphKit kit, ValueNode declaringClass, ValueNode receiver) {
+ ValueNode nonNullReceiver = kit.maybeCreateExplicitNullCheck(receiver);
+
+ LogicNode isInstance = kit.append(InstanceOfDynamicNode.create(kit.getAssumptions(), kit.getConstantReflection(), declaringClass, nonNullReceiver, false, false));
+ kit.startIf(isInstance, BranchProbabilityNode.FAST_PATH_PROFILE);
kit.elsePart();
- ConstantNode typeNode = kit.createConstant(kit.getConstantReflection().asJavaClass(type), JavaKind.Object);
- kit.createBytecodeExceptionObjectNode(BytecodeExceptionKind.CLASS_CAST, false, value, typeNode);
- illegalTypeEnds.add(kit.append(new EndNode()));
+
+ ResolvedJavaMethod exceptionCtor = kit.getMetaAccess().lookupJavaMethod(CLASS_CAST_EXCEPTION_CONSTRUCTOR);
+ ResolvedJavaMethod throwMethod = FactoryMethodSupport.singleton().lookup((UniverseMetaAccess) kit.getMetaAccess(), exceptionCtor, true);
+ kit.createInvokeWithExceptionAndUnwind(throwMethod, CallTargetNode.InvokeKind.Static, kit.getFrameState(), kit.bci());
+ kit.append(new LoweredDeadEndNode());
+
kit.endIf();
- Stamp checkedStamp = value.stamp(NodeView.DEFAULT).improveWith(StampFactory.objectNonNull(typeRef));
- return kit.unique(new PiNode(value, checkedStamp, ifNode.trueSuccessor()));
}
- /**
- * Creates {@linkplain ValueNode IR nodes} for the arguments passed to the JNI call. The
- * arguments do not include the receiver of the call, but only the actual arguments passed to
- * the JNI target function.
- *
- * @return List of created argument nodes and their type
- */
- private List> loadAndUnboxArguments(JNIGraphKit kit, HostedProviders providers, Signature invokeSignature) {
- MetaAccessProvider metaAccess = providers.getMetaAccess();
- List> args = new ArrayList<>();
- int javaIndex = argumentsJavaIndex(metaAccess);
- int count = invokeSignature.getParameterCount(false);
- // Windows and iOS CallVariant.VA_LIST is identical to CallVariant.ARRAY
- // iOS CallVariant.VARARGS stores values as an array on the stack
- if ((Platform.includedIn(Platform.DARWIN_AARCH64.class) && (callVariant == CallVariant.VARARGS || callVariant == CallVariant.VA_LIST)) ||
- (Platform.includedIn(Platform.WINDOWS.class) && callVariant == CallVariant.VA_LIST) || callVariant == CallVariant.ARRAY) {
- ResolvedJavaType elementType = metaAccess.lookupJavaType(JNIValue.class);
- int elementSize = SizeOf.get(JNIValue.class);
- ValueNode array;
- if (callVariant == CallVariant.VARARGS) {
- array = kit.append(new ReadCallerStackPointerNode());
- } else {
- array = kit.loadLocal(javaIndex, elementType.getJavaKind());
- }
- for (int i = 0; i < count; i++) {
- ResolvedJavaType type = (ResolvedJavaType) invokeSignature.getParameterType(i, null);
- JavaKind kind = type.getJavaKind();
- JavaKind readKind = callVariant == CallVariant.ARRAY ? kind : kind.getStackKind();
- if (readKind == JavaKind.Float && (callVariant == CallVariant.VARARGS || callVariant == CallVariant.VA_LIST)) {
- readKind = JavaKind.Double;
- }
- StructFieldInfo fieldInfo = getJNIValueOffsetOf(elementType, readKind);
- int offset = i * elementSize + fieldInfo.getOffsetInfo().getProperty();
- JavaKind wordKind = providers.getWordTypes().getWordKind();
- ConstantNode offsetConstant = kit.createConstant(JavaConstant.forIntegerKind(wordKind, offset), wordKind);
- OffsetAddressNode address = kit.unique(new OffsetAddressNode(array, offsetConstant));
- LocationIdentity locationIdentity = fieldInfo.getLocationIdentity();
- if (locationIdentity == null) {
- locationIdentity = LocationIdentity.any();
- }
- Stamp readStamp = getNarrowStamp(providers, readKind);
- ValueNode value = kit.append(new CInterfaceReadNode(address, locationIdentity, readStamp, BarrierType.NONE, "args[" + i + "]"));
- if (kind == JavaKind.Float && (callVariant == CallVariant.VARARGS || callVariant == CallVariant.VA_LIST)) {
- value = kit.unique(new FloatConvertNode(FloatConvert.D2F, value));
- } else if (kind.isObject()) {
- value = kit.unboxHandle(value);
- } else if (kind == JavaKind.Boolean) {
- value = convertToBoolean(kit, value);
- } else if (kind != kind.getStackKind() && callVariant == CallVariant.ARRAY) {
- value = maskSubWordValue(kit, value, kind);
- }
- args.add(Pair.create(value, type));
- }
- } else if (callVariant == CallVariant.VARARGS) {
- for (int i = 0; i < count; i++) {
- ResolvedJavaType type = (ResolvedJavaType) invokeSignature.getParameterType(i, null);
- JavaKind kind = type.getJavaKind();
- JavaKind loadKind = kind;
- if (loadKind == JavaKind.Float) { // C varargs promote float to double
- loadKind = JavaKind.Double;
- }
- ValueNode value = kit.loadLocal(javaIndex, loadKind);
- if (kind == JavaKind.Float) {
- value = kit.unique(new FloatConvertNode(FloatConvert.D2F, value));
- } else if (kind.isObject()) {
- value = kit.unboxHandle(value);
- } else if (kind == JavaKind.Boolean) {
- value = convertToBoolean(kit, value);
- }
- args.add(Pair.create(value, type));
- javaIndex += loadKind.getSlotCount();
- }
- } else if (callVariant == CallVariant.VA_LIST) {
- ValueNode valist = kit.loadLocal(javaIndex, metaAccess.lookupJavaType(WordBase.class).getJavaKind());
- for (int i = 0; i < count; i++) {
- ResolvedJavaType type = (ResolvedJavaType) invokeSignature.getParameterType(i, null);
- JavaKind kind = type.getJavaKind();
- JavaKind loadKind = kind.getStackKind();
- if (loadKind.isObject()) {
- loadKind = providers.getWordTypes().getWordKind();
- }
- ValueNode value = kit.append(new VaListNextArgNode(loadKind, valist));
- if (kind.isObject()) {
- value = kit.unboxHandle(value);
- } else if (kind == JavaKind.Boolean) {
- value = convertToBoolean(kit, value);
- }
- args.add(Pair.create(value, type));
- }
- } else {
- throw VMError.unsupportedFeature("Call variant: " + callVariant);
- }
- return args;
- }
+ private static ValueNode createNewObjectCall(JNIGraphKit kit, Signature invokeSignature, ValueNode newObjectAddress, ValueNode[] args) {
+ ConstantNode abstractTypeSentinel = kit.createWord(JNIAccessibleMethod.NEW_OBJECT_INVALID_FOR_ABSTRACT_TYPE);
+ kit.startIf(IntegerEqualsNode.create(newObjectAddress, abstractTypeSentinel, NodeView.DEFAULT), BranchProbabilityNode.SLOW_PATH_PROFILE);
+ kit.thenPart();
+ ResolvedJavaMethod exceptionCtor = kit.getMetaAccess().lookupJavaMethod(INSTANTIATION_EXCEPTION_CONSTRUCTOR);
+ ResolvedJavaMethod throwMethod = FactoryMethodSupport.singleton().lookup((UniverseMetaAccess) kit.getMetaAccess(), exceptionCtor, true);
+ kit.createInvokeWithExceptionAndUnwind(throwMethod, CallTargetNode.InvokeKind.Static, kit.getFrameState(), kit.bci());
+ kit.append(new LoweredDeadEndNode());
+ kit.endIf();
- /**
- * Returns the index of the frame state local for the first argument.
- */
- private int argumentsJavaIndex(MetaAccessProvider metaAccess) {
- return metaAccess.lookupJavaType(JNIEnvironment.class).getJavaKind().getSlotCount() +
- metaAccess.lookupJavaType(JNIObjectHandle.class).getJavaKind().getSlotCount() +
- (nonVirtual ? metaAccess.lookupJavaType(JNIObjectHandle.class).getJavaKind().getSlotCount() : 0) +
- metaAccess.lookupJavaType(JNIMethodId.class).getJavaKind().getSlotCount();
+ return createMethodCall(kit, invokeSignature.getReturnType(null), invokeSignature.toParameterTypes(null), newObjectAddress, args);
}
- /** Converts 0 to {@code false}, and 1-255 to {@code true}. */
- private static ValueNode convertToBoolean(JNIGraphKit kit, ValueNode value) {
- ValueNode maskedValue = maskSubWordValue(kit, value, JavaKind.Boolean);
- LogicNode isZero = IntegerEqualsNode.create(maskedValue, ConstantNode.forInt(0), NodeView.DEFAULT);
- return kit.append(ConditionalNode.create(isZero, ConstantNode.forBoolean(false), ConstantNode.forBoolean(true), NodeView.DEFAULT));
- }
+ private static ValueNode createMethodCall(JNIGraphKit kit, JavaType returnType, JavaType[] paramTypes, ValueNode methodAddress, ValueNode[] args) {
+ StampPair returnStamp = StampFactory.forDeclaredType(kit.getAssumptions(), returnType, false);
+ CallTargetNode callTarget = new IndirectCallTargetNode(methodAddress, args, returnStamp, paramTypes,
+ null, SubstrateCallingConventionKind.Java.toType(true), CallTargetNode.InvokeKind.Static);
- /** Masks a sub-word value to ensure that unused high bits are indeed cleared. */
- private static ValueNode maskSubWordValue(JNIGraphKit kit, ValueNode value, JavaKind kind) {
- assert kind != kind.getStackKind();
- ValueNode narrow = kit.append(NarrowNode.create(value, kind.getByteCount() * Byte.SIZE, NodeView.DEFAULT));
- if (kind.isUnsigned()) {
- return kit.append(ZeroExtendNode.create(narrow, Integer.SIZE, NodeView.DEFAULT));
- } else {
- return kit.append(SignExtendNode.create(narrow, Integer.SIZE, NodeView.DEFAULT));
- }
+ InvokeWithExceptionNode invoke = kit.startInvokeWithException(callTarget, kit.getFrameState(), kit.bci());
+ kit.exceptionPart();
+ ExceptionObjectNode exception = kit.exceptionObject();
+ kit.append(new UnwindNode(exception));
+ kit.endInvokeWithException();
+ return invoke;
}
- private static Stamp getNarrowStamp(HostedProviders providers, JavaKind kind) {
- if (kind != kind.getStackKind()) {
- // avoid widened stamp to prevent reading undefined bits
- return StampFactory.forInteger(kind.getByteCount() * Byte.SIZE);
- } else if (kind.isObject()) {
- ResolvedJavaType objectHandle = providers.getMetaAccess().lookupJavaType(JNIObjectHandle.class);
- return providers.getWordTypes().getWordStamp(objectHandle);
- } else {
- return StampFactory.forKind(kind);
- }
+ private static ValueNode mergeValues(JNIGraphKit kit, AbstractMergeNode merge, int bci, ValueNode... values) {
+ Stamp stamp = StampTool.meet(List.of(values));
+ ValueNode returnValue = kit.getGraph().addWithoutUnique(new ValuePhiNode(stamp, merge, values));
+ JavaKind returnKind = returnValue.getStackKind();
+ kit.getFrameState().push(returnKind, returnValue);
+ merge.setStateAfter(kit.getFrameState().create(bci, merge));
+ kit.getFrameState().pop(returnKind);
+ return returnValue;
}
- private StructFieldInfo getJNIValueOffsetOf(ResolvedJavaType jniValueType, JavaKind kind) {
- String name = String.valueOf(kind.isObject() ? 'l' : Character.toLowerCase(kind.getTypeChar()));
- StructInfo structInfo = (StructInfo) nativeLibs.findElementInfo(jniValueType);
- for (ElementInfo elementInfo : structInfo.getChildren()) {
- if (elementInfo instanceof StructFieldInfo) {
- StructFieldInfo fieldInfo = (StructFieldInfo) elementInfo;
- if (name.equals(fieldInfo.getName())) {
- return fieldInfo;
- }
+ private static ValueNode[] loadAndUnboxArguments(JNIGraphKit kit, HostedProviders providers, Signature invokeSignature, int firstSlotIndex) {
+ int slotIndex = firstSlotIndex;
+ int count = invokeSignature.getParameterCount(false);
+ ValueNode[] args = new ValueNode[count];
+ for (int i = 0; i < args.length; i++) {
+ ResolvedJavaType type = (ResolvedJavaType) invokeSignature.getParameterType(i, null);
+ JavaKind kind = type.getJavaKind();
+ JavaKind loadKind = kind;
+ if (kind.isObject()) {
+ loadKind = providers.getWordTypes().getWordKind();
+ } else if (kind != kind.getStackKind()) {
+ // We widened the kind in the signature for better reusability of call variant
+ // wrappers (read above) and need to mask extra bits below.
+ loadKind = kind.getStackKind();
}
+ ValueNode value = kit.loadLocal(slotIndex, loadKind);
+ if (kind.isObject()) {
+ value = kit.unboxHandle(value);
+ value = kit.checkObjectType(value, type, false);
+ } else if (kind != loadKind) {
+ value = kit.maskNumericIntBytes(value, kind);
+ }
+ args[i] = value;
+ slotIndex += loadKind.getSlotCount();
}
- throw VMError.shouldNotReachHere();
+ return args;
}
}
diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperMethod.java
index 05515cbdab8a..0b53c25a39e7 100644
--- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperMethod.java
+++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperMethod.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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
@@ -29,19 +29,11 @@
import java.util.ArrayList;
import java.util.List;
-import org.graalvm.compiler.core.common.type.ObjectStamp;
-import org.graalvm.compiler.core.common.type.StampFactory;
-import org.graalvm.compiler.core.common.type.TypeReference;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.nodes.ConstantNode;
import org.graalvm.compiler.nodes.InvokeWithExceptionNode;
-import org.graalvm.compiler.nodes.LogicNode;
-import org.graalvm.compiler.nodes.PiNode;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.ValueNode;
-import org.graalvm.compiler.nodes.extended.BytecodeExceptionNode.BytecodeExceptionKind;
-import org.graalvm.compiler.nodes.extended.GuardingNode;
-import org.graalvm.compiler.nodes.java.InstanceOfNode;
import org.graalvm.compiler.nodes.java.MonitorEnterNode;
import org.graalvm.compiler.nodes.java.MonitorExitNode;
import org.graalvm.compiler.nodes.java.MonitorIdNode;
@@ -63,7 +55,6 @@
import jdk.vm.ci.meta.Constant;
import jdk.vm.ci.meta.JavaConstant;
-import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.JavaType;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
@@ -80,17 +71,14 @@ class JNINativeCallWrapperMethod extends CustomSubstitutionMethod {
JNINativeCallWrapperMethod(ResolvedJavaMethod method) {
super(method);
- linkage = createLinkage(method);
+ assert !(method instanceof WrappedJavaMethod);
+ this.linkage = createLinkage(method);
}
private static JNINativeLinkage createLinkage(ResolvedJavaMethod method) {
- ResolvedJavaMethod unwrapped = method;
- while (unwrapped instanceof WrappedJavaMethod) {
- unwrapped = ((WrappedJavaMethod) unwrapped).getWrapped();
- }
- String className = unwrapped.getDeclaringClass().getName();
- String descriptor = unwrapped.getSignature().toMethodDescriptor();
- return JNIAccessFeature.singleton().makeLinkage(className, unwrapped.getName(), descriptor);
+ String className = method.getDeclaringClass().getName();
+ String descriptor = method.getSignature().toMethodDescriptor();
+ return JNIAccessFeature.singleton().makeLinkage(className, method.getName(), descriptor);
}
@Override
@@ -185,25 +173,10 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method,
kit.rethrowPendingException();
if (javaReturnType.getJavaKind().isObject()) {
// Just before return to always run the epilogue and never suppress a pending exception
- returnValue = castObject(kit, returnValue, (ResolvedJavaType) javaReturnType);
+ returnValue = kit.checkObjectType(returnValue, (ResolvedJavaType) javaReturnType, false);
}
kit.createReturn(returnValue, javaReturnType.getJavaKind());
return kit.finalizeGraph();
}
-
- private static ValueNode castObject(JNIGraphKit kit, ValueNode object, ResolvedJavaType type) {
- ValueNode casted = object;
- if (!type.isJavaLangObject()) { // safe cast to expected type
- TypeReference typeRef = TypeReference.createTrusted(kit.getAssumptions(), type);
- LogicNode condition = kit.append(InstanceOfNode.createAllowNull(typeRef, object, null, null));
- if (!condition.isTautology()) {
- ObjectStamp stamp = StampFactory.object(typeRef, false);
- ValueNode expectedClass = kit.createConstant(kit.getConstantReflection().asJavaClass(type), JavaKind.Object);
- GuardingNode guard = kit.createCheckThrowingBytecodeException(condition, false, BytecodeExceptionKind.CLASS_CAST, object, expectedClass);
- casted = kit.append(PiNode.create(object, stamp, guard.asNode()));
- }
- }
- return casted;
- }
}