From ec7682d71fae1861af60ea99c1724b6f0b19ae5a Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 30 Jun 2022 15:51:13 +0200 Subject: [PATCH] Call Java methods from JNI via vtable or by address without per-method stubs. --- .../svm/hosted/meta/KnownOffsetsFeature.java | 2 +- .../src/com/oracle/svm/jni/JNIJavaCalls.java | 42 --- .../svm/jni/access/JNIAccessFeature.java | 41 ++- .../svm/jni/access/JNIAccessibleClass.java | 1 + .../svm/jni/access/JNIAccessibleMethod.java | 69 +++-- .../jni/access/JNIReflectionDictionary.java | 1 + .../svm/jni/hosted/JNICallSignature.java | 62 +++-- .../oracle/svm/jni/hosted/JNIGraphKit.java | 36 ++- .../svm/jni/hosted/JNIJavaCallMethod.java | 249 ----------------- .../JNIJavaCallVariantWrapperMethod.java | 26 +- .../jni/hosted/JNIJavaCallWrapperMethod.java | 259 +++++++++++++----- 11 files changed, 363 insertions(+), 425 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCalls.java delete mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallMethod.java 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 c79d3fcae7abf..564c7a5ee86e2 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/JNIJavaCalls.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCalls.java deleted file mode 100644 index 3ee5b3ea12bc0..0000000000000 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCalls.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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; - -import jdk.vm.ci.meta.ConstantPool; -import jdk.vm.ci.meta.MetaAccessProvider; - -/** Holder class for generated {@link com.oracle.svm.jni.hosted.JNIJavaCallMethod} code. */ -public final class JNIJavaCalls { - /** - * 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(JNIJavaCalls.class).getDeclaredConstructors()[0].getConstantPool(); - } - - private JNIJavaCalls() { - } -} 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 e073aa13d035f..cc20bd5d1a549 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; @@ -63,14 +64,15 @@ import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.ProgressReporter; import com.oracle.svm.hosted.code.CEntryPointData; +import com.oracle.svm.hosted.code.FactoryMethodSupport; import com.oracle.svm.hosted.config.ConfigurationParserUtils; +import com.oracle.svm.hosted.meta.KnownOffsetsFeature; import com.oracle.svm.hosted.meta.MaterializedConstantFields; import com.oracle.svm.hosted.substitute.SubstitutionReflectivityFilter; import com.oracle.svm.jni.JNIJavaCallTrampolines; import com.oracle.svm.jni.hosted.JNICallSignature; import com.oracle.svm.jni.hosted.JNICallTrampolineMethod; import com.oracle.svm.jni.hosted.JNIFieldAccessorMethod; -import com.oracle.svm.jni.hosted.JNIJavaCallMethod; import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod; import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod.CallVariant; import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod; @@ -129,6 +131,12 @@ private void abortIfSealed() { UserError.guarantee(!sealed, "Classes, methods and fields must be registered for JNI access before the analysis has completed."); } + @Override + public List> 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; @@ -178,8 +186,8 @@ public void beforeAnalysis(BeforeAnalysisAccess arg) { if (!ImageSingletons.contains(JNIFieldAccessorMethod.Factory.class)) { ImageSingletons.add(JNIFieldAccessorMethod.Factory.class, new JNIFieldAccessorMethod.Factory()); } - if (!ImageSingletons.contains(JNIJavaCallMethod.Factory.class)) { - ImageSingletons.add(JNIJavaCallMethod.Factory.class, new JNIJavaCallMethod.Factory()); + if (!ImageSingletons.contains(JNIJavaCallWrapperMethod.Factory.class)) { + ImageSingletons.add(JNIJavaCallWrapperMethod.Factory.class, new JNIJavaCallWrapperMethod.Factory()); } BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) arg; @@ -302,14 +310,25 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) { JNIAccessibleMethodDescriptor descriptor = JNIAccessibleMethodDescriptor.of(method); jniClass.addMethodIfAbsent(descriptor, d -> { AnalysisUniverse universe = access.getUniverse(); - ResolvedJavaMethod targetMethod = universe.getOriginalMetaAccess().lookupJavaMethod(method); - - WordTypes wordTypes = access.getBigBang().getProviders().getWordTypes(); - JNIJavaCallMethod javaCallMethod = ImageSingletons.lookup(JNIJavaCallMethod.Factory.class).create(targetMethod, universe, wordTypes); - access.registerAsRoot(universe.lookup(javaCallMethod), true); + MetaAccessProvider originalMetaAccess = universe.getOriginalMetaAccess(); + ResolvedJavaMethod targetMethod = originalMetaAccess.lookupJavaMethod(method); + + JNIJavaCallWrapperMethod.Factory factory = ImageSingletons.lookup(JNIJavaCallWrapperMethod.Factory.class); + AnalysisMethod aTargetMethod = universe.lookup(targetMethod); + if (!targetMethod.isConstructor() || factory.canInvokeConstructorOnObject(targetMethod, originalMetaAccess)) { + access.registerAsRoot(aTargetMethod, false); + } // else: function pointers will be an error stub + + ResolvedJavaMethod newObjectMethod = null; + if (targetMethod.isConstructor() && !targetMethod.getDeclaringClass().isAbstract()) { + var aFactoryMethod = (AnalysisMethod) FactoryMethodSupport.singleton().lookup(access.getMetaAccess(), aTargetMethod, false); + access.registerAsRoot(aFactoryMethod, true); + newObjectMethod = aFactoryMethod.getWrapped(); + } - JNIJavaCallWrapperMethod callWrapperMethod = javaCallWrapperMethods.computeIfAbsent(javaCallMethod.getSignature(), - signature -> new JNIJavaCallWrapperMethod(signature, universe.getOriginalMetaAccess(), wordTypes)); + JNICallSignature compatibleSignature = JNIJavaCallWrapperMethod.getGeneralizedSignatureForTarget(targetMethod, originalMetaAccess); + JNIJavaCallWrapperMethod callWrapperMethod = javaCallWrapperMethods.computeIfAbsent(compatibleSignature, + signature -> factory.create(signature, originalMetaAccess, access.getBigBang().getProviders().getWordTypes())); access.registerAsRoot(universe.lookup(callWrapperMethod), true); JNIJavaCallVariantWrapperGroup variantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), false); @@ -317,7 +336,7 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) { if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isAbstract(method.getModifiers())) { nonvirtualVariantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), true); } - return new JNIAccessibleMethod(d, method.getModifiers(), jniClass, javaCallMethod, callWrapperMethod, + return new JNIAccessibleMethod(d, jniClass, targetMethod, newObjectMethod, callWrapperMethod, variantWrappers.varargs, variantWrappers.array, variantWrappers.valist, nonvirtualVariantWrappers.varargs, nonvirtualVariantWrappers.array, nonvirtualVariantWrappers.valist); }); 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 c06d0b01045ed..69ccde2315581 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/JNIAccessibleMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleMethod.java index f38f196d821ba..a8735bc9e387c 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,19 +26,24 @@ import java.lang.reflect.Modifier; +import org.graalvm.compiler.nodes.NamedLocationIdentity; +import org.graalvm.compiler.word.BarrieredAccess; import org.graalvm.nativeimage.Platform.HOSTED_ONLY; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.nativeimage.c.function.CodePointer; +import org.graalvm.word.PointerBase; +import org.graalvm.word.WordFactory; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.annotate.AlwaysInline; import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.graal.meta.KnownOffsets; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl.CompilationAccessImpl; +import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedUniverse; -import com.oracle.svm.jni.hosted.JNIJavaCallMethod; import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod; import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod.CallVariant; import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod; @@ -53,6 +58,9 @@ * Information on a method that can be looked up and called via JNI. */ public final class JNIAccessibleMethod extends JNIAccessibleMember { + public static final int STATICALLY_BOUND_METHOD = -1; + public static final int VTABLE_OFFSET_NOT_YET_COMPUTED = -2; + public static final int NEW_OBJECT_INVALID_FOR_ABSTRACT_TYPE = -1; static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAccess, CallVariant variant, boolean nonVirtual) { StringBuilder name = new StringBuilder(32); @@ -74,7 +82,9 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces @Platforms(HOSTED_ONLY.class) private final JNIAccessibleMethodDescriptor descriptor; private final int modifiers; - private CodePointer javaCall; + private int vtableOffset = VTABLE_OFFSET_NOT_YET_COMPUTED; + private CodePointer nonvirtualTarget; + private PointerBase newObjectTarget; // for constructors private CodePointer callWrapper; @SuppressWarnings("unused") private CFunctionPointer varargsWrapper; @SuppressWarnings("unused") private CFunctionPointer arrayWrapper; @@ -82,7 +92,8 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces @SuppressWarnings("unused") private CFunctionPointer varargsNonvirtualWrapper; @SuppressWarnings("unused") private CFunctionPointer arrayNonvirtualWrapper; @SuppressWarnings("unused") private CFunctionPointer valistNonvirtualWrapper; - @Platforms(HOSTED_ONLY.class) private final JNIJavaCallMethod javaCallMethod; + @Platforms(HOSTED_ONLY.class) private final ResolvedJavaMethod targetMethod; + @Platforms(HOSTED_ONLY.class) private final ResolvedJavaMethod newObjectTargetMethod; @Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod callWrapperMethod; @Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod varargsWrapperMethod; @Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod arrayWrapperMethod; @@ -92,9 +103,9 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces @Platforms(HOSTED_ONLY.class) private final JNIJavaCallVariantWrapperMethod valistNonvirtualWrapperMethod; JNIAccessibleMethod(JNIAccessibleMethodDescriptor descriptor, - int modifiers, JNIAccessibleClass declaringClass, - JNIJavaCallMethod javaCallMethod, + ResolvedJavaMethod targetMethod, + ResolvedJavaMethod newObjectTargetMethod, JNIJavaCallWrapperMethod callWrapperMethod, JNIJavaCallVariantWrapperMethod varargsWrapper, JNIJavaCallVariantWrapperMethod arrayWrapper, @@ -103,13 +114,14 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces JNIJavaCallVariantWrapperMethod arrayNonvirtualWrapper, JNIJavaCallVariantWrapperMethod valistNonvirtualWrapper) { super(declaringClass); - assert javaCallMethod != null && callWrapperMethod != null && varargsWrapper != null && arrayWrapper != null && valistWrapper != null; - assert (Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers)) // + assert callWrapperMethod != null && varargsWrapper != null && arrayWrapper != null && valistWrapper != null; + assert (targetMethod.isStatic() || targetMethod.isAbstract()) // ? (varargsNonvirtualWrapper == null && arrayNonvirtualWrapper == null && valistNonvirtualWrapper == null) : (varargsNonvirtualWrapper != null & arrayNonvirtualWrapper != null && valistNonvirtualWrapper != null); this.descriptor = descriptor; - this.modifiers = modifiers; - this.javaCallMethod = javaCallMethod; + this.modifiers = targetMethod.getModifiers(); + this.targetMethod = targetMethod; + this.newObjectTargetMethod = newObjectTargetMethod; this.callWrapperMethod = callWrapperMethod; this.varargsWrapperMethod = varargsWrapper; this.arrayWrapperMethod = arrayWrapper; @@ -119,18 +131,31 @@ static ResolvedJavaField getCallVariantWrapperField(MetaAccessProvider metaAcces this.valistNonvirtualWrapperMethod = valistNonvirtualWrapper; } - @AlwaysInline("Work around an issue with the LLVM backend with which the return value was accessed incorrectly.") - @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) - public CodePointer getJavaCallAddress() { - return javaCall; - } - @AlwaysInline("Work around an issue with the LLVM backend with which the return value was accessed incorrectly.") @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) public CodePointer getCallWrapperAddress() { return callWrapper; } + @AlwaysInline("Work around an issue with the LLVM backend with which the return value was accessed incorrectly.") + public CodePointer getJavaCallAddress(Object instance, boolean nonVirtual) { + if (!nonVirtual) { + assert vtableOffset != JNIAccessibleMethod.VTABLE_OFFSET_NOT_YET_COMPUTED; + if (vtableOffset != JNIAccessibleMethod.STATICALLY_BOUND_METHOD) { + return BarrieredAccess.readWord(instance.getClass(), vtableOffset, NamedLocationIdentity.FINAL_LOCATION); + } + } + return nonvirtualTarget; + } + + public PointerBase getNewObjectAddress() { + return newObjectTarget; + } + + public Class getDeclaringClassObject() { + return getDeclaringClass().getClassObject(); + } + boolean isPublic() { return Modifier.isPublic(modifiers); } @@ -143,7 +168,19 @@ boolean isStatic() { void finishBeforeCompilation(CompilationAccessImpl access) { HostedUniverse hUniverse = access.getUniverse(); AnalysisUniverse aUniverse = access.getUniverse().getBigBang().getUniverse(); - javaCall = new MethodPointer(hUniverse.lookup(aUniverse.lookup(javaCallMethod))); + HostedMethod hTarget = hUniverse.lookup(aUniverse.lookup(targetMethod)); + if (hTarget.canBeStaticallyBound()) { + vtableOffset = STATICALLY_BOUND_METHOD; + } else { + vtableOffset = KnownOffsets.singleton().getVTableOffset(hTarget.getVTableIndex()); + } + nonvirtualTarget = new MethodPointer(hTarget); + if (newObjectTargetMethod != null) { + newObjectTarget = new MethodPointer(hUniverse.lookup(aUniverse.lookup(newObjectTargetMethod))); + } else if (targetMethod.isConstructor()) { + assert targetMethod.getDeclaringClass().isAbstract(); + newObjectTarget = WordFactory.signed(NEW_OBJECT_INVALID_FOR_ABSTRACT_TYPE); + } callWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(callWrapperMethod))); varargsWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(varargsWrapperMethod))); arrayWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(arrayWrapperMethod))); 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 b619632625b66..dde3ac6e32d08 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 @@ -246,6 +246,7 @@ private static JNIMethodId toMethodID(JNIAccessibleMethod method) { return (JNIMethodId) value; } + @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) public static JNIAccessibleMethod getMethodByID(JNIMethodId method) { return (JNIAccessibleMethod) getObjectFromMethodID(method); } diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallSignature.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallSignature.java index 722abc7e2fa08..65b6b6ec0dbe2 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallSignature.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallSignature.java @@ -27,6 +27,9 @@ import java.util.Arrays; import java.util.Objects; +import com.oracle.graal.pointsto.infrastructure.WrappedJavaType; +import com.oracle.svm.core.SubstrateUtil; + import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.MetaAccessProvider; @@ -35,60 +38,69 @@ public class JNICallSignature implements Signature { - private final JavaKind[] parameterKinds; - private final JavaKind returnKind; - private final MetaAccessProvider originalMetaAccess; + private final JavaType[] paramTypes; + private final JavaType returnType; - JNICallSignature(JavaKind[] parameterKinds, JavaKind returnKind, MetaAccessProvider originalMetaAccess) { - this.parameterKinds = parameterKinds; - this.returnKind = returnKind; - this.originalMetaAccess = originalMetaAccess; + JNICallSignature(JavaType[] paramTypes, JavaType returnType) { + assert Arrays.stream(paramTypes).noneMatch(WrappedJavaType.class::isInstance) && !(returnType instanceof WrappedJavaType); + this.paramTypes = paramTypes; + this.returnType = returnType; } - public String getIdentifier() { - StringBuilder sb = new StringBuilder(1 + parameterKinds.length); - sb.append(returnKind.getTypeChar()); - for (JavaKind kind : parameterKinds) { - sb.append(kind.getTypeChar()); + JNICallSignature(JavaKind[] paramKinds, JavaKind returnKind, MetaAccessProvider originalMetaAccess) { + this.paramTypes = new ResolvedJavaType[paramKinds.length]; + for (int i = 0; i < paramKinds.length; i++) { + this.paramTypes[i] = resolveType(paramKinds[i], originalMetaAccess); } - return sb.toString(); + this.returnType = resolveType(returnKind, originalMetaAccess); } - @Override - public int getParameterCount(boolean receiver) { - return parameterKinds.length; + private static ResolvedJavaType resolveType(JavaKind kind, MetaAccessProvider metaAccess) { + return metaAccess.lookupJavaType(kind.isObject() ? Object.class : kind.toJavaClass()); } - private ResolvedJavaType resolveType(JavaKind kind) { - Class clazz = Object.class; - if (!kind.isObject()) { - clazz = kind.toJavaClass(); + public String getIdentifier() { + StringBuilder sb = new StringBuilder(1 + paramTypes.length); + boolean digest = false; + for (JavaType type : paramTypes) { + if (type.getJavaKind().isPrimitive() || (type instanceof ResolvedJavaType && ((ResolvedJavaType) type).isJavaLangObject())) { + sb.append(type.getJavaKind().getTypeChar()); + } else { + sb.append(type.toClassName()); + digest = true; + } } - return originalMetaAccess.lookupJavaType(clazz); + sb.append('_').append(returnType.getJavaKind().getTypeChar()); + return digest ? SubstrateUtil.digest(sb.toString()) : sb.toString(); + } + + @Override + public int getParameterCount(boolean receiver) { + return paramTypes.length; } @Override public JavaType getParameterType(int index, ResolvedJavaType accessingClass) { - return resolveType(parameterKinds[index]); + return paramTypes[index]; } @Override public JavaType getReturnType(ResolvedJavaType accessingClass) { - return resolveType(returnKind); + return returnType; } @Override public boolean equals(Object obj) { if (this != obj && obj instanceof JNICallSignature) { var other = (JNICallSignature) obj; - return Arrays.equals(parameterKinds, other.parameterKinds) && Objects.equals(returnKind, other.returnKind); + return Arrays.equals(paramTypes, other.paramTypes) && Objects.equals(returnType, other.returnType); } return (this == obj); } @Override public int hashCode() { - return Arrays.hashCode(parameterKinds) * 31 + Objects.hashCode(returnKind); + return Arrays.hashCode(paramTypes) * 31 + Objects.hashCode(returnType); } @Override 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 5c02be3a190c3..c6e1bf337d7fc 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 @@ -153,24 +153,42 @@ 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 invokeJNIMethodMethod(methodId, "getCallWrapperAddress"); + return invokeJNIMethodObjectMethod("getCallWrapperAddress", methodId); } - public InvokeWithExceptionNode getJavaCallAddressFromMethodId(ValueNode methodId) { - return invokeJNIMethodMethod(methodId, "getJavaCallAddress"); + 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 + * 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 InvokeWithExceptionNode invokeJNIMethodMethod(ValueNode methodId, String name) { + 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))); - PiNode pi = createPiNode(methodObj, stamp); - return createInvokeWithExceptionAndUnwind(findMethod(JNIAccessibleMethod.class, name), InvokeKind.Special, getFrameState(), bci(), pi); + 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() { @@ -210,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/JNIJavaCallMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallMethod.java deleted file mode 100644 index 67c24bfb6fcef..0000000000000 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallMethod.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * 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 - * 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.lang.reflect.Constructor; -import java.util.Arrays; -import java.util.List; - -import org.graalvm.compiler.core.common.type.Stamp; -import org.graalvm.compiler.core.common.type.StampFactory; -import org.graalvm.compiler.debug.DebugContext; -import org.graalvm.compiler.java.FrameStateBuilder; -import org.graalvm.compiler.nodes.AbstractMergeNode; -import org.graalvm.compiler.nodes.CallTargetNode; -import org.graalvm.compiler.nodes.ConstantNode; -import org.graalvm.compiler.nodes.LogicNode; -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.IntegerEqualsNode; -import org.graalvm.compiler.nodes.calc.ObjectEqualsNode; -import org.graalvm.compiler.nodes.extended.BranchProbabilityNode; -import org.graalvm.compiler.nodes.type.StampTool; -import org.graalvm.compiler.word.WordTypes; - -import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; -import com.oracle.graal.pointsto.meta.HostedProviders; -import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.graal.nodes.LoweredDeadEndNode; -import com.oracle.svm.hosted.code.FactoryMethodSupport; -import com.oracle.svm.hosted.code.NonBytecodeStaticMethod; -import com.oracle.svm.jni.JNIJavaCalls; -import com.oracle.svm.util.ReflectionUtil; - -import jdk.vm.ci.meta.Constant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.JavaType; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; -import jdk.vm.ci.meta.Signature; - -/** - * Generated method that is called by a {@link JNIJavaCallWrapperMethod} to invoke a specific Java - * method, potentially non-virtually, and in the case of constructors, potentially allocating a new - * object in the process. - */ -public class JNIJavaCallMethod extends NonBytecodeStaticMethod { - - public static class Factory { - public JNIJavaCallMethod create(ResolvedJavaMethod method, AnalysisUniverse analysisUniverse, WordTypes wordTypes) { - return new JNIJavaCallMethod(method, analysisUniverse, wordTypes); - } - } - - private static final Constructor INSTANTIATION_EXCEPTION_CONSTRUCTOR = ReflectionUtil.lookupConstructor(InstantiationException.class); - - private static JNICallSignature getSignatureForTarget(ResolvedJavaMethod targetMethod, AnalysisUniverse analysisUniverse, WordTypes wordTypes) { - Signature originalSignature = targetMethod.getSignature(); - int paramCount = originalSignature.getParameterCount(false); - JavaKind[] paramKinds = new JavaKind[2 + paramCount]; - paramKinds[0] = JavaKind.Boolean; // non-virtual (== true)? - paramKinds[1] = JavaKind.Object; // receiver or class obj - for (int i = 0; i < paramCount; i++) { - JavaType paramType = analysisUniverse.lookupAllowUnresolved(originalSignature.getParameterType(i, null)); - /* - * We use only kinds and no specific object types so that a call wrapper can be reused - * with any object types. Therefore, we have to do type checks in this method. - */ - JavaKind paramKind = wordTypes.asKind(paramType); - /* - * Widen to the stack kind, i.e. from boolean/byte/short/char to int, for greater - * reusability of call wrappers. This also changes the parameter type taken from the C - * caller in the wrapper method, 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 that are passed parameters in jvalue - * arrays, we can just mask the extra bits in this method thanks to little endian order. - */ - paramKinds[2 + i] = paramKind.getStackKind(); - } - JavaType returnType = analysisUniverse.lookupAllowUnresolved(originalSignature.getReturnType(null)); - JavaKind returnKind = wordTypes.asKind(returnType); - 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 all our supported 64-bit calling conventions. - returnKind = JavaKind.Long; - } - return new JNICallSignature(paramKinds, returnKind, analysisUniverse.getOriginalMetaAccess()); - } - - private final ResolvedJavaMethod targetMethod; - - public JNIJavaCallMethod(ResolvedJavaMethod targetMethod, AnalysisUniverse analysisUniverse, WordTypes wordTypes) { - super("invoke_" + SubstrateUtil.uniqueShortName(targetMethod), - analysisUniverse.getOriginalMetaAccess().lookupJavaType(JNIJavaCalls.class), - getSignatureForTarget(targetMethod, analysisUniverse, wordTypes), - JNIJavaCalls.getConstantPool(analysisUniverse.getOriginalMetaAccess())); - this.targetMethod = targetMethod; - } - - @Override - public JNICallSignature getSignature() { - return (JNICallSignature) super.getSignature(); - } - - @Override - public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) { - JNIGraphKit kit = new JNIGraphKit(debug, providers, method); - - ResolvedJavaMethod invokeMethod = targetMethod; - UniverseMetaAccess metaAccess = (UniverseMetaAccess) providers.getMetaAccess(); - if (metaAccess.getWrapped() instanceof UniverseMetaAccess) { - invokeMethod = ((UniverseMetaAccess) metaAccess.getWrapped()).getUniverse().lookup(invokeMethod); - } - invokeMethod = metaAccess.getUniverse().lookup(invokeMethod); - - ValueNode[] args; - List incomingArgs = kit.loadArguments(toParameterTypes()); - Signature invokeSignature = invokeMethod.getSignature(); - for (int i = 2; i < incomingArgs.size(); i++) { - var paramType = (ResolvedJavaType) invokeSignature.getParameterType(i - 2, null); - if (!paramType.isPrimitive() && !paramType.isJavaLangObject()) { - incomingArgs.set(i, kit.checkObjectType(incomingArgs.get(i), paramType, false)); - } else if (paramType.getJavaKind().getStackKind() == JavaKind.Int) { - // We might have widened the kind in the signature for better reusability of call - // wrappers (read above) and need to mask extra bits now. - JavaKind paramKind = paramType.getJavaKind(); - incomingArgs.set(i, kit.maskNumericIntBytes(incomingArgs.get(i), paramKind)); - } - } - int firstArg = invokeMethod.hasReceiver() ? 1 : 2; - args = incomingArgs.subList(firstArg, incomingArgs.size()).toArray(ValueNode[]::new); - - ValueNode returnValue; - boolean canBeStaticallyBound = invokeMethod.canBeStaticallyBound(); - if (canBeStaticallyBound || invokeMethod.isAbstract()) { - returnValue = doInvoke(providers, kit, invokeMethod, canBeStaticallyBound, args.clone()); - } else { - ValueNode nonVirtual = incomingArgs.get(0); - LogicNode isVirtualCall = kit.unique(IntegerEqualsNode.create(nonVirtual, kit.createInt(0), NodeView.DEFAULT)); - - kit.startIf(isVirtualCall, BranchProbabilityNode.FAST_PATH_PROFILE); - kit.thenPart(); - ValueNode resultVirtual = doInvoke(providers, kit, invokeMethod, false, args.clone()); - - kit.elsePart(); - ValueNode resultNonVirtual = doInvoke(providers, kit, invokeMethod, true, args.clone()); - - AbstractMergeNode merge = kit.endIf(); - merge.setStateAfter(kit.getFrameState().create(kit.bci(), merge)); - - ValueNode[] resultValues = {resultVirtual, resultNonVirtual}; - Stamp stamp = StampTool.meet(Arrays.asList(resultValues)); - returnValue = stamp.hasValues() ? kit.unique(new ValuePhiNode(stamp, merge, resultValues)) : null; - } - JavaKind returnKind = getSignature().getReturnKind(); - if (returnKind == JavaKind.Long) { - // We might have widened to a long return type for better reusability of call wrappers - if (returnValue == null || returnValue.stamp(NodeView.DEFAULT).isEmpty()) { - returnValue = kit.createLong(0); // void method, return something - } else { - returnValue = kit.widenNumericInt(returnValue, JavaKind.Long); - } - } - kit.createReturn(returnValue, returnKind); - return kit.finalizeGraph(); - } - - private ValueNode doInvoke(HostedProviders providers, JNIGraphKit kit, ResolvedJavaMethod invokeMethod, boolean nonVirtual, ValueNode[] args) { - UniverseMetaAccess metaAccess = (UniverseMetaAccess) providers.getMetaAccess(); - CallTargetNode.InvokeKind invokeKind = invokeMethod.isStatic() ? CallTargetNode.InvokeKind.Static : // - ((nonVirtual || invokeMethod.isConstructor()) ? CallTargetNode.InvokeKind.Special : CallTargetNode.InvokeKind.Virtual); - ValueNode result; - 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 look at the second JNI call - * parameter, which is either `jobject obj` (the receiver object) in the case of - * `CallMethod`, or `jclass clazz` (hub of the receiver object) for `NewObject`. - */ - ResolvedJavaType receiverClass = invokeMethod.getDeclaringClass(); - Constant hub = providers.getConstantReflection().asObjectHub(receiverClass); - ConstantNode hubNode = kit.createConstant(hub, JavaKind.Object); - ObjectEqualsNode isNewObjectCall = kit.unique(new ObjectEqualsNode(args[0], hubNode)); - kit.startIf(isNewObjectCall, BranchProbabilityNode.FAST_PATH_PROFILE); - kit.thenPart(); - ValueNode createdObject = null; - if (invokeMethod.getDeclaringClass().isAbstract()) { - ResolvedJavaMethod throwMethod = FactoryMethodSupport.singleton().lookup(metaAccess, metaAccess.lookupJavaMethod(INSTANTIATION_EXCEPTION_CONSTRUCTOR), true); - createMethodCall(kit, throwMethod, CallTargetNode.InvokeKind.Static, kit.getFrameState()); - kit.append(new LoweredDeadEndNode()); - } else { - ResolvedJavaMethod factoryMethod = FactoryMethodSupport.singleton().lookup(metaAccess, invokeMethod, false); - ValueNode[] argsWithoutReceiver = Arrays.copyOfRange(args, 1, args.length); - createdObject = createMethodCall(kit, factoryMethod, CallTargetNode.InvokeKind.Static, kit.getFrameState(), argsWithoutReceiver); - } - - kit.elsePart(); - args[0] = kit.checkObjectType(args[0], invokeMethod.getDeclaringClass(), true); - createMethodCall(kit, invokeMethod, invokeKind, kit.getFrameState(), args); - - AbstractMergeNode merge = kit.endIf(); - if (merge != null) { - merge.setStateAfter(kit.getFrameState().create(kit.bci(), merge)); - result = kit.unique(new ValuePhiNode(StampFactory.object(), merge, new ValueNode[]{createdObject, args[0]})); - } else { - result = args[0]; - } - } else { - if (invokeMethod.hasReceiver()) { - args[0] = kit.checkObjectType(args[0], invokeMethod.getDeclaringClass(), true); - } - result = createMethodCall(kit, invokeMethod, invokeKind, kit.getFrameState(), args); - } - return result; - } - - protected ValueNode createMethodCall(JNIGraphKit kit, ResolvedJavaMethod invokeMethod, CallTargetNode.InvokeKind invokeKind, FrameStateBuilder frameState, ValueNode... args) { - return kit.createInvokeWithExceptionAndUnwind(invokeMethod, invokeKind, frameState, kit.bci(), args); - } - -} 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 index e0e9d2fb2beca..db1d7029896cc 100644 --- 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 @@ -50,8 +50,9 @@ import org.graalvm.word.LocationIdentity; import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess; -import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; 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; @@ -61,6 +62,7 @@ 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.meta.HostedMetaAccess; import com.oracle.svm.jni.JNIJavaCallVariantWrappers; import jdk.vm.ci.meta.JavaConstant; @@ -135,12 +137,12 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, UniverseMetaAccess metaAccess = (UniverseMetaAccess) providers.getMetaAccess(); JNIGraphKit kit = new JNIGraphKit(debug, providers, method); - Signature invokeSignature = callWrapperSignature; - if (metaAccess.getWrapped() instanceof UniverseMetaAccess) { - invokeSignature = ((UniverseMetaAccess) metaAccess.getWrapped()).getUniverse().lookup( - invokeSignature, (WrappedJavaType) ((WrappedJavaMethod) method).getWrapped().getDeclaringClass()); + 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()); } - invokeSignature = metaAccess.getUniverse().lookup(invokeSignature, (WrappedJavaType) method.getDeclaringClass()); JavaKind wordKind = providers.getWordTypes().getWordKind(); int slotIndex = 0; @@ -163,12 +165,18 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, 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; @@ -216,7 +224,7 @@ private List loadArguments(JNIGraphKit kit, HostedProviders providers } for (int i = firstParamIndex; i < count; i++) { JavaKind kind = invokeSignature.getParameterKind(i); - assert kind == kind.getStackKind() : "other conversions and bit masking must happen in JNIJavaCallMethod"; + 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; @@ -243,7 +251,7 @@ private List loadArguments(JNIGraphKit kit, HostedProviders providers } else if (callVariant == CallVariant.VARARGS) { for (int i = firstParamIndex; i < count; i++) { JavaKind kind = invokeSignature.getParameterKind(i); - assert kind == kind.getStackKind() : "other conversions and bit masking must happen in JNIJavaCallMethod"; + 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) @@ -262,7 +270,7 @@ private List loadArguments(JNIGraphKit kit, HostedProviders providers if (kind.isObject()) { kind = wordKind; } - assert kind == kind.getStackKind() : "other conversions and bit masking must happen in JNIJavaCallMethod"; + 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); } 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 1c6aadb8b97d2..988f111a02794 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 @@ -24,43 +24,59 @@ */ package com.oracle.svm.jni.hosted; +import java.lang.reflect.Constructor; +import java.util.List; + +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.java.FrameStateBuilder; 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.LogicNode; import org.graalvm.compiler.nodes.NodeView; +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.IntegerEqualsNode; +import org.graalvm.compiler.nodes.calc.ObjectEqualsNode; +import org.graalvm.compiler.nodes.extended.BranchProbabilityNode; import org.graalvm.compiler.nodes.java.ExceptionObjectNode; +import org.graalvm.compiler.nodes.java.InstanceOfDynamicNode; +import org.graalvm.compiler.nodes.type.StampTool; import org.graalvm.compiler.word.WordTypes; import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess; -import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; 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.LoweredDeadEndNode; +import com.oracle.svm.hosted.code.FactoryMethodSupport; import com.oracle.svm.hosted.code.NonBytecodeStaticMethod; +import com.oracle.svm.hosted.meta.HostedMetaAccess; import com.oracle.svm.jni.JNIJavaCallWrappers; +import com.oracle.svm.jni.access.JNIAccessibleMethod; +import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; /** * 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. It delegates to a generated - * {@link JNIJavaCallMethod} for the actual call to a particular Java method. + * object handle arguments and boxing an object return value. * * @see Java 8 JNI @@ -69,27 +85,67 @@ * JNI functions documentation */ public class JNIJavaCallWrapperMethod extends NonBytecodeStaticMethod { - private final Signature javaCallSignature; + 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(JNICallSignature targetSignature, MetaAccessProvider originalMetaAccess, WordTypes wordTypes) { + return new JNIJavaCallWrapperMethod(targetSignature, originalMetaAccess, wordTypes); + } + + @SuppressWarnings("unused") + public boolean canInvokeConstructorOnObject(ResolvedJavaMethod constructor, MetaAccessProvider originalMetaAccess) { + return true; + } + } + + public static JNICallSignature 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 JNICallSignature(paramTypes, returnType); + } + + private final Signature targetSignature; - public JNIJavaCallWrapperMethod(JNICallSignature javaCallSignature, MetaAccessProvider metaAccess, WordTypes wordTypes) { - super("invoke" + javaCallSignature.getIdentifier(), metaAccess.lookupJavaType(JNIJavaCallWrappers.class), - createSignature(javaCallSignature, metaAccess, wordTypes), JNIJavaCallWrappers.getConstantPool(metaAccess)); - this.javaCallSignature = javaCallSignature; + protected JNIJavaCallWrapperMethod(JNICallSignature 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 JNICallSignature createSignature(Signature targetSignature, MetaAccessProvider originalMetaAccess, WordTypes wordTypes) { JavaKind wordKind = wordTypes.getWordKind(); int count = targetSignature.getParameterCount(false); - JavaKind[] args = new JavaKind[3 + count - 2]; + 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 = 2; i < count; i++) { // skip non-virtual, receiver/class arguments + for (int i = 0; i < count; i++) { // skip non-virtual, receiver/class arguments JavaKind kind = targetSignature.getParameterKind(i); if (kind.isObject()) { kind = wordKind; // handle } - args[3 + (i - 2)] = kind.getStackKind(); + /* + * 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(); } JavaKind returnKind = targetSignature.getReturnKind(); if (returnKind.isObject()) { @@ -108,12 +164,12 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, UniverseMetaAccess metaAccess = (UniverseMetaAccess) providers.getMetaAccess(); JNIGraphKit kit = new JNIGraphKit(debug, providers, method); - Signature invokeSignature = javaCallSignature; - if (metaAccess.getWrapped() instanceof UniverseMetaAccess) { - invokeSignature = ((UniverseMetaAccess) metaAccess.getWrapped()).getUniverse().lookup( - invokeSignature, (WrappedJavaType) ((WrappedJavaMethod) method).getWrapped().getDeclaringClass()); + 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()); } - invokeSignature = metaAccess.getUniverse().lookup(invokeSignature, (WrappedJavaType) method.getDeclaringClass()); JavaKind wordKind = providers.getWordTypes().getWordKind(); int slotIndex = 0; @@ -125,17 +181,10 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, ValueNode nonVirtual = kit.loadLocal(slotIndex, JavaKind.Boolean.getStackKind()); slotIndex += JavaKind.Boolean.getStackKind().getSlotCount(); - int firstParamIndex = 2; - ValueNode[] loadedArgs = loadAndUnboxArguments(kit, providers, invokeSignature, firstParamIndex, slotIndex); + ValueNode[] args = loadAndUnboxArguments(kit, providers, invokeSignature, slotIndex); + ValueNode returnValue = createCall(kit, invokeSignature, methodId, receiverOrClass, nonVirtual, args); - ValueNode[] args = new ValueNode[2 + loadedArgs.length]; - args[0] = nonVirtual; - args[1] = receiverOrClass; - System.arraycopy(loadedArgs, 0, args, 2, loadedArgs.length); - - ValueNode javaCallAddress = kit.getJavaCallAddressFromMethodId(methodId); - ValueNode returnValue = createMethodCall(kit, invokeSignature.toParameterTypes(null), invokeSignature.getReturnType(null), kit.getFrameState(), javaCallAddress, args); - JavaKind returnKind = (returnValue != null) ? returnValue.getStackKind() : JavaKind.Void; + JavaKind returnKind = returnValue.getStackKind(); if (returnKind.isObject()) { returnValue = kit.boxObjectInLocalHandle(returnValue); } @@ -143,64 +192,144 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, return kit.finalizeGraph(); } - /** - * Builds a JNI {@code CallMethod} call, returning a node that contains the return value - * or null/zero/false if an exception occurred (in which case the exception becomes a JNI - * pending exception). - */ - protected ValueNode createMethodCall(JNIGraphKit kit, JavaType[] paramTypes, JavaType returnType, FrameStateBuilder state, ValueNode address, ValueNode... args) { - int bci = kit.bci(); - InvokeWithExceptionNode invoke = startInvokeWithRetainedException(kit, paramTypes, returnType, state, bci, address, args); - AbstractMergeNode invokeMerge = kit.endInvokeWithException(); - - if (invoke.getStackKind() == JavaKind.Void) { - invokeMerge.setStateAfter(state.create(bci, invokeMerge)); - return null; + 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); } - ValueNode exceptionValue = kit.unique(ConstantNode.defaultForKind(invoke.getStackKind())); - ValueNode[] inputs = {invoke, exceptionValue}; - ValueNode returnValue = kit.getGraph().addWithoutUnique(new ValuePhiNode(invoke.stamp(NodeView.DEFAULT), invokeMerge, inputs)); - JavaKind returnKind = returnValue.getStackKind(); - state.push(returnKind, returnValue); - invokeMerge.setStateAfter(state.create(bci, invokeMerge)); - state.pop(returnKind); - return returnValue; + 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); } - protected static InvokeWithExceptionNode startInvokeWithRetainedException(JNIGraphKit kit, JavaType[] paramTypes, - JavaType returnType, FrameStateBuilder state, int bci, ValueNode methodAddress, ValueNode... args) { - ValueNode formerPendingException = kit.getAndClearPendingException(); + 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); + } - StampPair returnStamp = StampFactory.forDeclaredType(kit.getAssumptions(), returnType, false); - CallTargetNode callTarget = new IndirectCallTargetNode(methodAddress, args, returnStamp, - paramTypes, null, SubstrateCallingConventionKind.Java.toType(true), InvokeKind.Static); - InvokeWithExceptionNode invoke = kit.startInvokeWithException(callTarget, state, bci); + 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); - kit.noExceptionPart(); // no new exception was thrown, restore the formerly pending one - kit.setPendingException(formerPendingException); + kit.elsePart(); + createConstructorCall(kit, invokeSignature, methodId, declaringClass, receiverOrClass, args); - kit.exceptionPart(); - ExceptionObjectNode exceptionObject = kit.exceptionObject(); - kit.setPendingException(exceptionObject); + AbstractMergeNode merge = kit.endIf(); + return mergeValues(kit, merge, kit.bci(), createdObject, receiverOrClass); + } + 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); + } + + private static ValueNode createMethodCallWithReceiver(JNIGraphKit kit, Signature invokeSignature, ValueNode declaringClass, ValueNode methodAddress, ValueNode receiver, ValueNode[] args) { + dynamicTypeCheckReceiver(kit, declaringClass, receiver); + + 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 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(); + + 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(); + } + + 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(); + + return createMethodCall(kit, invokeSignature.getReturnType(null), invokeSignature.toParameterTypes(null), newObjectAddress, args); + } + + 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); + + 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 ValueNode[] loadAndUnboxArguments(JNIGraphKit kit, HostedProviders providers, Signature invokeSignature, int firstParamIndex, int firstSlotIndex) { + 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 static ValueNode[] loadAndUnboxArguments(JNIGraphKit kit, HostedProviders providers, Signature invokeSignature, int firstSlotIndex) { int slotIndex = firstSlotIndex; int count = invokeSignature.getParameterCount(false); - ValueNode[] args = new ValueNode[count - firstParamIndex]; + ValueNode[] args = new ValueNode[count]; for (int i = 0; i < args.length; i++) { - JavaKind kind = invokeSignature.getParameterKind(firstParamIndex + i); - assert kind == kind.getStackKind() : "conversions and bit masking must happen in JNIJavaCallMethod"; + 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();