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: *

    - *
  1. {@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.
  2. - *
  3. Because all {@link JNIJavaCallWrapperMethod call wrappers} are entry points, the call - * wrappers and any code that is reachable through them is compiled.
  4. - *
  5. Before compilation, a {@link CFunctionPointer} is created for each call wrapper that is - * eventually filled with the call wrapper's final entry point address.
  6. - *
  7. 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.
  8. + *
  9. 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.
  10. + *
  11. {@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}.
  12. + *
  13. 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.
  14. + *
  15. 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.
  16. *
*

*/ 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; - } }