From 8197d39f2910f6b1b3a48267fdcfa8953dca4312 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 15 Jun 2022 21:35:35 +0200 Subject: [PATCH 01/11] Share JNINativeCallWrapperMethods between native callees with the same signature. --- .../oracle/svm/jni/JNINativeCallWrappers.java | 42 +++++ .../svm/jni/hosted/JNICallSignature.java | 98 +++++++++++ .../svm/jni/hosted/JNICallWrapperFeature.java | 16 +- .../oracle/svm/jni/hosted/JNIGraphKit.java | 64 ++++++- .../jni/hosted/JNIJavaCallWrapperMethod.java | 31 +--- .../svm/jni/hosted/JNINativeCallMethod.java | 156 ++++++++++++++++++ ...> JNINativeCallSubstitutionProcessor.java} | 38 +++-- .../hosted/JNINativeCallWrapperMethod.java | 155 +++++------------ 8 files changed, 432 insertions(+), 168 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNINativeCallWrappers.java create mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallSignature.java create mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java rename substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/{JNINativeCallWrapperSubstitutionProcessor.java => JNINativeCallSubstitutionProcessor.java} (59%) diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNINativeCallWrappers.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNINativeCallWrappers.java new file mode 100644 index 000000000000..a668c202f629 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNINativeCallWrappers.java @@ -0,0 +1,42 @@ +/* + * 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 {@code JNINativeCallWrapperMethod}s. */ +public final class JNINativeCallWrappers { + /** + * 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(JNINativeCallWrappers.class).getDeclaredConstructors()[0].getConstantPool(); + } + + private JNINativeCallWrappers() { + } +} 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 new file mode 100644 index 000000000000..722abc7e2fa0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallSignature.java @@ -0,0 +1,98 @@ +/* + * 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.Arrays; +import java.util.Objects; + +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; + +public class JNICallSignature implements Signature { + + private final JavaKind[] parameterKinds; + private final JavaKind returnKind; + private final MetaAccessProvider originalMetaAccess; + + JNICallSignature(JavaKind[] parameterKinds, JavaKind returnKind, MetaAccessProvider originalMetaAccess) { + this.parameterKinds = parameterKinds; + this.returnKind = returnKind; + this.originalMetaAccess = originalMetaAccess; + } + + public String getIdentifier() { + StringBuilder sb = new StringBuilder(1 + parameterKinds.length); + sb.append(returnKind.getTypeChar()); + for (JavaKind kind : parameterKinds) { + sb.append(kind.getTypeChar()); + } + return sb.toString(); + } + + @Override + public int getParameterCount(boolean receiver) { + return parameterKinds.length; + } + + private ResolvedJavaType resolveType(JavaKind kind) { + Class clazz = Object.class; + if (!kind.isObject()) { + clazz = kind.toJavaClass(); + } + return originalMetaAccess.lookupJavaType(clazz); + } + + @Override + public JavaType getParameterType(int index, ResolvedJavaType accessingClass) { + return resolveType(parameterKinds[index]); + } + + @Override + public JavaType getReturnType(ResolvedJavaType accessingClass) { + return resolveType(returnKind); + } + + @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 (this == obj); + } + + @Override + public int hashCode() { + return Arrays.hashCode(parameterKinds) * 31 + Objects.hashCode(returnKind); + } + + @Override + public String toString() { + return getIdentifier(); + } +} 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..e434ef927d00 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,23 +27,25 @@ import java.util.Arrays; import java.util.List; +import org.graalvm.nativeimage.c.function.CFunction; import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.nativeimage.hosted.Feature; -import com.oracle.svm.core.jni.JNIRuntimeAccess; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.jni.access.JNIAccessFeature; import com.oracle.svm.jni.access.JNIAccessibleMethod; +import com.oracle.svm.jni.nativeapi.JNINativeMethod; /** * Responsible for generating JNI call wrappers for Java-to-native and native-to-Java invocations. * *

- * Java-to-native call wrappers are created by {@link JNINativeCallWrapperSubstitutionProcessor}. It - * creates a {@link JNINativeCallWrapperMethod} for each Java method that is declared with the - * {@code native} keyword and that was registered via {@link JNIRuntimeAccess} to be accessible via - * JNI at runtime. The method provides a graph that performs the native code invocation. This graph - * is visible to the analysis. + * Java-to-native call wrappers are created by {@link JNINativeCallSubstitutionProcessor}. It + * creates a {@link JNINativeMethod} for each reachable Java method with the {@code native} keyword + * (except those handled by other mechanisms such as {@link CFunction}). This method then invokes a + * {@link JNINativeCallWrapperMethod} that matches the native method's signature, which is shared + * between all native methods with that signature and that produces a graph that performs the native + * code invocation and is visible to the analysis. *

* *

@@ -74,6 +76,6 @@ public List> getRequiredFeatures() { @Override public void duringSetup(DuringSetupAccess arg) { DuringSetupAccessImpl access = (DuringSetupAccessImpl) arg; - access.registerNativeSubstitutionProcessor(new JNINativeCallWrapperSubstitutionProcessor(access)); + access.registerNativeSubstitutionProcessor(new JNINativeCallSubstitutionProcessor(access)); } } 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..e4246586154f 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,12 +24,27 @@ */ package com.oracle.svm.jni.hosted; +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; @@ -37,6 +52,7 @@ 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 +63,46 @@ 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 a sub-long value to ensure that unused high bits are indeed cleared. */ + public ValueNode maskIntegerBits(ValueNode value, JavaKind kind) { + assert kind.isNumericInteger(); + if (kind == JavaKind.Long) { + return value; + } + int bits = kind.getByteCount() * Byte.SIZE; + ValueNode narrowed = append(NarrowNode.create(value, bits, NodeView.DEFAULT)); + if (kind == JavaKind.Boolean) { + LogicNode isZero = IntegerEqualsNode.create(narrowed, ConstantNode.forIntegerBits(bits, 0), NodeView.DEFAULT); + return append(ConditionalNode.create(isZero, ConstantNode.forBoolean(false), ConstantNode.forBoolean(true), NodeView.DEFAULT)); + } + int stackBits = kind.getStackKind().getBitCount(); + if (kind.isUnsigned()) { + return append(ZeroExtendNode.create(narrowed, stackBits, NodeView.DEFAULT)); + } else { + return append(SignExtendNode.create(narrowed, stackBits, NodeView.DEFAULT)); + } + } + private InvokeWithExceptionNode createStaticInvoke(String name, ValueNode... args) { return createInvokeWithExceptionAndUnwind(findMethod(JNIGeneratedMethodSupport.class, name, true), InvokeKind.Static, getFrameState(), bci(), args); } @@ -63,9 +119,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() { @@ -109,9 +163,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) { 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..9e2b278c9177 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 @@ -52,14 +52,9 @@ import org.graalvm.compiler.nodes.StructuredGraph; 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; @@ -487,9 +482,9 @@ private List> loadAndUnboxArguments(JNIGraphKi } else if (kind.isObject()) { value = kit.unboxHandle(value); } else if (kind == JavaKind.Boolean) { - value = convertToBoolean(kit, value); + value = kit.maskIntegerBits(value, JavaKind.Boolean); } else if (kind != kind.getStackKind() && callVariant == CallVariant.ARRAY) { - value = maskSubWordValue(kit, value, kind); + value = kit.maskIntegerBits(value, kind); } args.add(Pair.create(value, type)); } @@ -507,7 +502,7 @@ private List> loadAndUnboxArguments(JNIGraphKi } else if (kind.isObject()) { value = kit.unboxHandle(value); } else if (kind == JavaKind.Boolean) { - value = convertToBoolean(kit, value); + value = kit.maskIntegerBits(value, JavaKind.Boolean); } args.add(Pair.create(value, type)); javaIndex += loadKind.getSlotCount(); @@ -525,7 +520,7 @@ private List> loadAndUnboxArguments(JNIGraphKi if (kind.isObject()) { value = kit.unboxHandle(value); } else if (kind == JavaKind.Boolean) { - value = convertToBoolean(kit, value); + value = kit.maskIntegerBits(value, JavaKind.Boolean); } args.add(Pair.create(value, type)); } @@ -545,24 +540,6 @@ private int argumentsJavaIndex(MetaAccessProvider metaAccess) { metaAccess.lookupJavaType(JNIMethodId.class).getJavaKind().getSlotCount(); } - /** 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)); - } - - /** 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)); - } - } - private static Stamp getNarrowStamp(HostedProviders providers, JavaKind kind) { if (kind != kind.getStackKind()) { // avoid widened stamp to prevent reading undefined bits diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java new file mode 100644 index 000000000000..6522330c5f71 --- /dev/null +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java @@ -0,0 +1,156 @@ +/* + * 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.Field; +import java.lang.reflect.Modifier; +import java.util.List; + +import org.graalvm.compiler.debug.DebugContext; +import org.graalvm.compiler.java.FrameStateBuilder; +import org.graalvm.compiler.nodes.CallTargetNode; +import org.graalvm.compiler.nodes.ConstantNode; +import org.graalvm.compiler.nodes.StructuredGraph; +import org.graalvm.compiler.nodes.UnwindNode; +import org.graalvm.compiler.nodes.ValueNode; +import org.graalvm.compiler.nodes.java.MonitorEnterNode; +import org.graalvm.compiler.nodes.java.MonitorExitNode; +import org.graalvm.compiler.nodes.java.MonitorIdNode; + +import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess; +import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; +import com.oracle.graal.pointsto.meta.HostedProviders; +import com.oracle.svm.core.graal.nodes.CGlobalDataLoadAddressNode; +import com.oracle.svm.hosted.annotation.CustomSubstitutionMethod; +import com.oracle.svm.hosted.heap.SVMImageHeapScanner; +import com.oracle.svm.jni.access.JNIAccessFeature; +import com.oracle.svm.jni.access.JNINativeLinkage; +import com.oracle.svm.util.ReflectionUtil; + +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; + +/** + * Generated code for calling a specific native method from Java code by finding its entry point and + * delegating to a fitting {@link JNINativeCallWrapperMethod}. + */ +class JNINativeCallMethod extends CustomSubstitutionMethod { + private final Field linkageBuiltInAddressField = ReflectionUtil.lookupField(JNINativeLinkage.class, "builtInAddress"); + + private final JNINativeLinkage linkage; + private final JNINativeCallWrapperMethod wrapperMethod; + + JNINativeCallMethod(ResolvedJavaMethod method, JNINativeCallWrapperMethod wrapperMethod) { + super(method); + this.linkage = createLinkage(method); + this.wrapperMethod = wrapperMethod; + } + + private static JNINativeLinkage createLinkage(ResolvedJavaMethod method) { + assert !(method instanceof WrappedJavaMethod); + String className = method.getDeclaringClass().getName(); + String descriptor = method.getSignature().toMethodDescriptor(); + return JNIAccessFeature.singleton().makeLinkage(className, method.getName(), descriptor); + } + + @Override + public int getModifiers() { + // A synchronized method requires some special handling. Instead, if the native method is + // declared synchronized, we add graph nodes to lock and unlock accordingly. + return getOriginal().getModifiers() & ~Modifier.SYNCHRONIZED; + } + + @Override + public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) { + JNIGraphKit kit = new JNIGraphKit(debug, providers, method); + ValueNode callAddress; + if (linkage.isBuiltInFunction()) { + callAddress = kit.unique(new CGlobalDataLoadAddressNode(linkage.getBuiltInAddress())); + SVMImageHeapScanner.instance().rescanField(linkage, linkageBuiltInAddressField); + } else { + callAddress = kit.nativeCallAddress(kit.createObject(linkage)); + } + + JavaType[] params = method.toParameterTypes(); + List args = kit.loadArguments(params); + + int nargs = (method.isStatic() ? 2 : 1) + args.size(); // static must be passed class object + ValueNode[] wrapperArgs = new ValueNode[nargs]; + wrapperArgs[0] = callAddress; + int i = 1; + if (method.isStatic()) { + JavaConstant clazz = providers.getConstantReflection().asJavaClass(method.getDeclaringClass()); + wrapperArgs[i++] = ConstantNode.forConstant(clazz, providers.getMetaAccess(), kit.getGraph()); + } + for (ValueNode arg : args) { + wrapperArgs[i++] = arg; + } + + if (getOriginal().isSynchronized()) { + ValueNode monitorObject = wrapperArgs[1]; + MonitorIdNode monitorId = kit.add(new MonitorIdNode(kit.getFrameState().lockDepth(false))); + MonitorEnterNode monitorEnter = kit.append(new MonitorEnterNode(monitorObject, monitorId)); + kit.getFrameState().pushLock(monitorEnter.object(), monitorEnter.getMonitorId()); + monitorEnter.setStateAfter(kit.getFrameState().create(kit.bci(), monitorEnter)); + } + + ResolvedJavaMethod universeWrapperMethod = wrapperMethod; + UniverseMetaAccess metaAccess = (UniverseMetaAccess) providers.getMetaAccess(); + if (metaAccess.getWrapped() instanceof UniverseMetaAccess) { + universeWrapperMethod = ((UniverseMetaAccess) metaAccess.getWrapped()).getUniverse().lookup(universeWrapperMethod); + } + universeWrapperMethod = metaAccess.getUniverse().lookup(universeWrapperMethod); + ValueNode returnValue = kit.startInvokeWithException(universeWrapperMethod, CallTargetNode.InvokeKind.Static, kit.getFrameState(), kit.bci(), wrapperArgs); + kit.exceptionPart(); + maybeExitMonitor(kit, kit.getFrameState().copy()); + kit.append(new UnwindNode(kit.exceptionObject())); + kit.endInvokeWithException(); + + maybeExitMonitor(kit, kit.getFrameState()); + + JavaType returnType = method.getSignature().getReturnType(null); + JavaKind returnKind = returnType.getJavaKind(); + if (returnKind.isObject()) { + // Just before return to always run the epilogue and never suppress a pending exception + returnValue = kit.checkObjectType(returnValue, (ResolvedJavaType) returnType, false); + } else if (returnKind != JavaKind.Void && JNINativeCallWrapperMethod.returnKindWidensToLong(returnKind)) { + returnValue = kit.maskIntegerBits(returnValue, returnKind); + } + kit.createReturn(returnValue, returnKind); + return kit.finalizeGraph(); + } + + private void maybeExitMonitor(JNIGraphKit kit, FrameStateBuilder frameState) { + if (getOriginal().isSynchronized()) { + MonitorIdNode monitorId = frameState.peekMonitorId(); + ValueNode monitorObject = frameState.popLock(); + MonitorExitNode monitorExit = kit.append(new MonitorExitNode(monitorObject, monitorId, null)); + monitorExit.setStateAfter(frameState.create(kit.bci(), monitorExit)); + } + } +} diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallSubstitutionProcessor.java similarity index 59% rename from substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperSubstitutionProcessor.java rename to substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallSubstitutionProcessor.java index bbde0fb4a8d1..cfb06b996eaa 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallSubstitutionProcessor.java @@ -27,45 +27,53 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.graalvm.compiler.word.WordTypes; + import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.jni.JNIJavaCallTrampolines; import com.oracle.svm.jni.access.JNIAccessFeature; -import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; /** - * Substitutes methods declared as {@code native} with {@link JNINativeCallWrapperMethod} instances - * that take care of performing the actual native calls. + * Substitutes methods declared as {@code native} with {@link JNINativeCallMethod} and + * {@link JNINativeCallWrapperMethod} instances that perform the actual native calls. */ -class JNINativeCallWrapperSubstitutionProcessor extends SubstitutionProcessor { - private final MetaAccessProvider originalMetaAccess; +final class JNINativeCallSubstitutionProcessor extends SubstitutionProcessor { + private final AnalysisUniverse universe; + private final WordTypes wordTypes; + private final ResolvedJavaType trampolinesType; - private final Map callWrappers = new ConcurrentHashMap<>(); + private final Map callers = new ConcurrentHashMap<>(); + private final Map callWrappers = new ConcurrentHashMap<>(); - JNINativeCallWrapperSubstitutionProcessor(DuringSetupAccessImpl access) { - this.originalMetaAccess = access.getMetaAccess().getWrapped(); - this.trampolinesType = originalMetaAccess.lookupJavaType(JNIJavaCallTrampolines.class); + JNINativeCallSubstitutionProcessor(DuringSetupAccessImpl access) { + this.universe = access.getUniverse(); + this.wordTypes = access.getBigBang().getProviders().getWordTypes(); + this.trampolinesType = universe.getOriginalMetaAccess().lookupJavaType(JNIJavaCallTrampolines.class); } @Override public ResolvedJavaMethod lookup(ResolvedJavaMethod method) { assert method.isNative() : "Must have been registered as a native substitution processor"; - if (method instanceof JNINativeCallWrapperMethod) { // already substituted - return method; + if (method instanceof JNINativeCallMethod || method instanceof JNINativeCallWrapperMethod) { + return method; // already substituted } if (method.getDeclaringClass().equals(trampolinesType)) { - return JNIAccessFeature.singleton().getOrCreateCallTrampolineMethod(originalMetaAccess, method.getName()); + return JNIAccessFeature.singleton().getOrCreateCallTrampolineMethod(universe.getOriginalMetaAccess(), method.getName()); } - return callWrappers.computeIfAbsent(method, JNINativeCallWrapperMethod::new); + return callers.computeIfAbsent(method, original -> new JNINativeCallMethod(original, + callWrappers.computeIfAbsent(JNINativeCallWrapperMethod.getSignatureForTarget(original, universe, wordTypes), + signature -> new JNINativeCallWrapperMethod(signature, universe.getOriginalMetaAccess())))); } @Override public ResolvedJavaMethod resolve(ResolvedJavaMethod method) { - if (method instanceof JNINativeCallWrapperMethod) { - return ((JNINativeCallWrapperMethod) method).getOriginal(); + if (method instanceof JNINativeCallMethod) { + return ((JNINativeCallMethod) method).getOriginal(); } else if (method instanceof JNICallTrampolineMethod) { return ((JNICallTrampolineMethod) method).getOriginal(); } 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..ee5b4b7d8b98 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 @@ -24,120 +24,91 @@ */ package com.oracle.svm.jni.hosted; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; 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; +import org.graalvm.compiler.word.WordTypes; -import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.HostedProviders; -import com.oracle.svm.core.graal.nodes.CGlobalDataLoadAddressNode; -import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.core.thread.VMThreads.StatusSupport; -import com.oracle.svm.hosted.annotation.CustomSubstitutionMethod; +import com.oracle.svm.hosted.code.NonBytecodeStaticMethod; import com.oracle.svm.hosted.code.SimpleSignature; -import com.oracle.svm.hosted.heap.SVMImageHeapScanner; -import com.oracle.svm.jni.access.JNIAccessFeature; -import com.oracle.svm.jni.access.JNINativeLinkage; +import com.oracle.svm.jni.JNINativeCallWrappers; import com.oracle.svm.jni.nativeapi.JNIEnvironment; import com.oracle.svm.jni.nativeapi.JNIObjectHandle; -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; import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; /** - * Generated code for calling a specific native method from Java code. The wrapper takes care of - * transitioning to native code and back to Java, and if required, for boxing object arguments in - * handles and for unboxing an object return value. + * Generated code for calling native methods with a specific {@linkplain JNICallSignature form} from + * Java code. The wrapper takes care of transitioning to native code and back to Java, and if + * required, for boxing object arguments in handles, and for unboxing an object return value. */ -class JNINativeCallWrapperMethod extends CustomSubstitutionMethod { - private final JNINativeLinkage linkage; - private final Field linkageBuiltInAddressField = ReflectionUtil.lookupField(JNINativeLinkage.class, "builtInAddress"); - - JNINativeCallWrapperMethod(ResolvedJavaMethod method) { - super(method); - linkage = createLinkage(method); +class JNINativeCallWrapperMethod extends NonBytecodeStaticMethod { + + /** + * 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. + */ + static boolean returnKindWidensToLong(JavaKind returnKind) { + return returnKind.isNumericInteger() || returnKind == JavaKind.Void; } - private static JNINativeLinkage createLinkage(ResolvedJavaMethod method) { - ResolvedJavaMethod unwrapped = method; - while (unwrapped instanceof WrappedJavaMethod) { - unwrapped = ((WrappedJavaMethod) unwrapped).getWrapped(); + static JNICallSignature getSignatureForTarget(ResolvedJavaMethod method, AnalysisUniverse universe, WordTypes wordTypes) { + assert method.isNative() && !method.isConstructor(); + + Signature signature = method.getSignature(); + int count = signature.getParameterCount(false); + JavaKind[] params = new JavaKind[2 + count]; + params[0] = wordTypes.getWordKind(); // native target code address + params[1] = JavaKind.Object; // receiver or class object + for (int i = 0; i < count; i++) { + JavaType paramType = universe.lookupAllowUnresolved(signature.getParameterType(i, null)); + params[2 + i] = wordTypes.asKind(paramType); + } + JavaType returnType = universe.lookupAllowUnresolved(signature.getReturnType(null)); + JavaKind returnKind = wordTypes.asKind(returnType); + if (returnKindWidensToLong(returnKind)) { + returnKind = JavaKind.Long; } - String className = unwrapped.getDeclaringClass().getName(); - String descriptor = unwrapped.getSignature().toMethodDescriptor(); - return JNIAccessFeature.singleton().makeLinkage(className, unwrapped.getName(), descriptor); + return new JNICallSignature(params, returnKind, universe.getOriginalMetaAccess()); } - @Override - public int getModifiers() { - // A synchronized method requires some special handling. Instead, if the wrapped method is - // declared synchronized, we add graph nodes to lock and unlock accordingly. - return getOriginal().getModifiers() & ~Modifier.SYNCHRONIZED; + JNINativeCallWrapperMethod(JNICallSignature signature, MetaAccessProvider originalMetaAccess) { + super("invoke" + signature.getIdentifier(), originalMetaAccess.lookupJavaType(JNINativeCallWrappers.class), + signature, JNINativeCallWrappers.getConstantPool(originalMetaAccess)); } @Override public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) { JNIGraphKit kit = new JNIGraphKit(debug, providers, method); - StructuredGraph graph = kit.getGraph(); - InvokeWithExceptionNode handleFrame = kit.nativeCallPrologue(); - - ValueNode callAddress; - if (linkage.isBuiltInFunction()) { - callAddress = kit.unique(new CGlobalDataLoadAddressNode(linkage.getBuiltInAddress())); - SVMImageHeapScanner.instance().rescanField(linkage, linkageBuiltInAddressField); - } else { - callAddress = kit.nativeCallAddress(kit.createObject(linkage)); - } - ValueNode environment = kit.environment(); JavaType javaReturnType = method.getSignature().getReturnType(null); JavaType[] javaArgumentTypes = method.toParameterTypes(); List javaArguments = kit.loadArguments(javaArgumentTypes); + ValueNode callAddress = javaArguments.get(0); - List jniArguments = new ArrayList<>(2 + javaArguments.size()); - List jniArgumentTypes = new ArrayList<>(2 + javaArguments.size()); + List jniArguments = new ArrayList<>(1 + javaArguments.size()); + List jniArgumentTypes = new ArrayList<>(1 + javaArguments.size()); JavaType environmentType = providers.getMetaAccess().lookupJavaType(JNIEnvironment.class); JavaType objectHandleType = providers.getMetaAccess().lookupJavaType(JNIObjectHandle.class); jniArguments.add(environment); jniArgumentTypes.add(environmentType); - if (method.isStatic()) { - JavaConstant clazz = providers.getConstantReflection().asJavaClass(method.getDeclaringClass()); - ConstantNode clazzNode = ConstantNode.forConstant(clazz, providers.getMetaAccess(), graph); - ValueNode box = kit.boxObjectInLocalHandle(clazzNode); - jniArguments.add(box); - jniArgumentTypes.add(objectHandleType); - } - for (int i = 0; i < javaArguments.size(); i++) { + for (int i = 1; i < javaArguments.size(); i++) { ValueNode arg = javaArguments.get(i); JavaType argType = javaArgumentTypes[i]; - if (javaArgumentTypes[i].getJavaKind().isObject()) { + if (argType.getJavaKind().isObject()) { ValueNode obj = javaArguments.get(i); arg = kit.boxObjectInLocalHandle(obj); argType = objectHandleType; @@ -151,59 +122,17 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, jniReturnType = objectHandleType; } - if (getOriginal().isSynchronized()) { - ValueNode monitorObject; - if (method.isStatic()) { - Constant hubConstant = providers.getConstantReflection().asObjectHub(method.getDeclaringClass()); - DynamicHub hub = (DynamicHub) SubstrateObjectConstant.asObject(hubConstant); - monitorObject = ConstantNode.forConstant(SubstrateObjectConstant.forObject(hub), providers.getMetaAccess(), graph); - } else { - monitorObject = kit.maybeCreateExplicitNullCheck(javaArguments.get(0)); - } - MonitorIdNode monitorId = graph.add(new MonitorIdNode(kit.getFrameState().lockDepth(false))); - MonitorEnterNode monitorEnter = kit.append(new MonitorEnterNode(monitorObject, monitorId)); - kit.getFrameState().pushLock(monitorEnter.object(), monitorEnter.getMonitorId()); - monitorEnter.setStateAfter(kit.getFrameState().create(kit.bci(), monitorEnter)); - } - kit.getFrameState().clearLocals(); Signature jniSignature = new SimpleSignature(jniArgumentTypes, jniReturnType); ValueNode returnValue = kit.createCFunctionCall(callAddress, jniArguments, jniSignature, StatusSupport.STATUS_IN_NATIVE, false); - if (getOriginal().isSynchronized()) { - MonitorIdNode monitorId = kit.getFrameState().peekMonitorId(); - ValueNode monitorObject = kit.getFrameState().popLock(); - MonitorExitNode monitorExit = kit.append(new MonitorExitNode(monitorObject, monitorId, null)); - monitorExit.setStateAfter(kit.getFrameState().create(kit.bci(), monitorExit)); - } - if (javaReturnType.getJavaKind().isObject()) { returnValue = kit.unboxHandle(returnValue); // before destroying handles in epilogue } kit.nativeCallEpilogue(handleFrame); 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); - } 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; - } } From ccf349ca3c7035e71561079c670e97a6315167a6 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Sun, 19 Jun 2022 11:54:28 +0200 Subject: [PATCH 02/11] Share JNIJavaCallWrapperMethods between Java callees with the same signature. --- .../svm/jni/JNIGeneratedMethodSupport.java | 7 + .../src/com/oracle/svm/jni/JNIJavaCalls.java | 42 ++ .../svm/jni/access/JNIAccessFeature.java | 65 ++- .../svm/jni/access/JNIAccessibleMethod.java | 19 +- .../svm/jni/functions/JNIFunctions.java | 8 +- .../oracle/svm/jni/hosted/JNIGraphKit.java | 25 +- .../svm/jni/hosted/JNIJavaCallMethod.java | 249 ++++++++++ .../jni/hosted/JNIJavaCallWrapperMethod.java | 429 +++++------------- .../svm/jni/hosted/JNINativeCallMethod.java | 2 +- 9 files changed, 490 insertions(+), 356 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCalls.java create mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallMethod.java 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..8bb1e7c74434 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 @@ -26,6 +26,7 @@ import java.lang.reflect.Array; +import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.word.PointerBase; import org.graalvm.word.WordBase; @@ -35,8 +36,10 @@ import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.jni.access.JNIAccessibleField; import com.oracle.svm.jni.access.JNINativeLinkage; +import com.oracle.svm.jni.access.JNIReflectionDictionary; import com.oracle.svm.jni.nativeapi.JNIEnvironment; import com.oracle.svm.jni.nativeapi.JNIFieldId; +import com.oracle.svm.jni.nativeapi.JNIMethodId; import com.oracle.svm.jni.nativeapi.JNIObjectHandle; import jdk.internal.misc.Unsafe; @@ -76,6 +79,10 @@ static WordBase getFieldOffsetFromId(JNIFieldId fieldId) { return JNIAccessibleField.getOffsetFromId(fieldId); } + static CodePointer getJavaCallAddressFromMethodId(JNIMethodId methodId) { + return JNIReflectionDictionary.getMethodByID(methodId).getJavaCallAddress(); + } + static Object getStaticPrimitiveFieldsArray() { return StaticFieldsSupport.getStaticPrimitiveFields(); } 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 new file mode 100644 index 000000000000..3ee5b3ea12bc --- /dev/null +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCalls.java @@ -0,0 +1,42 @@ +/* + * 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 a7db01f7ad52..5bf7e1b09db6 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 @@ -37,6 +37,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 +47,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,14 +62,15 @@ 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.config.ConfigurationParserUtils; 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.JNIJavaCallWrapperMethod; import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod.CallVariant; import com.oracle.svm.util.ReflectionUtil; @@ -87,9 +90,24 @@ public static JNIAccessFeature singleton() { return ImageSingletons.lookup(JNIAccessFeature.class); } + static final class JNIJavaCallWrapperGroup { + static final JNIJavaCallWrapperGroup NONE = new JNIJavaCallWrapperGroup(null, null, null); + + final JNIJavaCallWrapperMethod varargs; + final JNIJavaCallWrapperMethod array; + final JNIJavaCallWrapperMethod valist; + + JNIJavaCallWrapperGroup(JNIJavaCallWrapperMethod varargs, JNIJavaCallWrapperMethod array, JNIJavaCallWrapperMethod 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 callWrappers = new ConcurrentHashMap<>(); + private final Map nonvirtualCallWrappers = new ConcurrentHashMap<>(); private int loadedConfigurations; @@ -161,9 +179,11 @@ public void beforeAnalysis(BeforeAnalysisAccess arg) { if (!ImageSingletons.contains(JNIJavaCallWrapperMethod.Factory.class)) { ImageSingletons.add(JNIJavaCallWrapperMethod.Factory.class, new JNIJavaCallWrapperMethod.Factory()); } + if (!ImageSingletons.contains(JNIJavaCallMethod.Factory.class)) { + ImageSingletons.add(JNIJavaCallMethod.Factory.class, new JNIJavaCallMethod.Factory()); + } BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) arg; - this.nativeLibraries = access.getNativeLibraries(); for (CallVariant variant : CallVariant.values()) { registerJavaCallTrampoline(access, variant, false); @@ -282,33 +302,40 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) { JNIAccessibleClass jniClass = addClass(method.getDeclaringClass(), access); JNIAccessibleMethodDescriptor descriptor = JNIAccessibleMethodDescriptor.of(method); jniClass.addMethodIfAbsent(descriptor, d -> { - JNIJavaCallWrapperMethod.Factory factory = ImageSingletons.lookup(JNIJavaCallWrapperMethod.Factory.class); - MetaAccessProvider wrappedMetaAccess = access.getMetaAccess().getWrapped(); + AnalysisUniverse universe = access.getUniverse(); + ResolvedJavaMethod targetMethod = universe.getOriginalMetaAccess().lookupJavaMethod(method); - 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); + WordTypes wordTypes = access.getBigBang().getProviders().getWordTypes(); + JNIJavaCallMethod javaCallMethod = ImageSingletons.lookup(JNIJavaCallMethod.Factory.class).create(targetMethod, universe, wordTypes); + access.registerAsRoot(universe.lookup(javaCallMethod), true); - JNIJavaCallWrapperMethod varargsNonvirtualCallWrapper = null; - JNIJavaCallWrapperMethod arrayNonvirtualCallWrapper = null; - JNIJavaCallWrapperMethod valistNonvirtualCallWrapper = null; + JNICallSignature javaCallSignature = javaCallMethod.getSignature(); + JNIJavaCallWrapperGroup wrappers = createJavaCallWrappers(access, javaCallSignature, false); + JNIJavaCallWrapperGroup nonvirtualWrappers = JNIJavaCallWrapperGroup.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)); + nonvirtualWrappers = createJavaCallWrappers(access, javaCallSignature, true); } + return new JNIAccessibleMethod(d, method.getModifiers(), jniClass, javaCallMethod, wrappers.varargs, wrappers.array, wrappers.valist, + nonvirtualWrappers.varargs, nonvirtualWrappers.array, nonvirtualWrappers.valist); + }); + } - JNIAccessibleMethod jniMethod = new JNIAccessibleMethod(d, method.getModifiers(), jniClass, varargsCallWrapper, arrayCallWrapper, valistCallWrapper, - varargsNonvirtualCallWrapper, arrayNonvirtualCallWrapper, valistNonvirtualCallWrapper); + private JNIJavaCallWrapperGroup createJavaCallWrappers(DuringAnalysisAccessImpl access, JNICallSignature javaCallSignature, boolean nonVirtual) { + var map = nonVirtual ? nonvirtualCallWrappers : callWrappers; + return map.computeIfAbsent(javaCallSignature, signature -> { + MetaAccessProvider originalMetaAccess = access.getUniverse().getOriginalMetaAccess(); + JNIJavaCallWrapperMethod.Factory factory = ImageSingletons.lookup(JNIJavaCallWrapperMethod.Factory.class); + JNIJavaCallWrapperMethod varargs = factory.create(signature, CallVariant.VARARGS, nonVirtual, originalMetaAccess); + JNIJavaCallWrapperMethod array = factory.create(signature, CallVariant.ARRAY, nonVirtual, originalMetaAccess); + JNIJavaCallWrapperMethod valist = factory.create(signature, CallVariant.VA_LIST, nonVirtual, originalMetaAccess); + 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 JNIJavaCallWrapperGroup(varargs, array, valist); }); } 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..c61bdbbeb26a 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 @@ -29,12 +29,14 @@ 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 com.oracle.graal.pointsto.meta.AnalysisUniverse; 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.HostedUniverse; +import com.oracle.svm.jni.hosted.JNIJavaCallMethod; import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod; import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod.CallVariant; @@ -48,7 +50,7 @@ */ public final class JNIAccessibleMethod extends JNIAccessibleMember { - public static ResolvedJavaField getCallWrapperField(MetaAccessProvider metaAccess, CallVariant variant, boolean nonVirtual) { + static ResolvedJavaField getCallWrapperField(MetaAccessProvider metaAccess, CallVariant variant, boolean nonVirtual) { StringBuilder name = new StringBuilder(32); if (variant == CallVariant.VARARGS) { name.append("varargs"); @@ -72,12 +74,14 @@ public static ResolvedJavaField getCallWrapperField(MetaAccessProvider metaAcces @Platforms(HOSTED_ONLY.class) private final JNIAccessibleMethodDescriptor descriptor; private final int modifiers; + private CodePointer javaCall; @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 JNIJavaCallMethod javaCallMethod; @Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod varargsCallWrapperMethod; @Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod arrayCallWrapperMethod; @Platforms(HOSTED_ONLY.class) private final JNIJavaCallWrapperMethod valistCallWrapperMethod; @@ -88,6 +92,7 @@ public static ResolvedJavaField getCallWrapperField(MetaAccessProvider metaAcces JNIAccessibleMethod(JNIAccessibleMethodDescriptor descriptor, int modifiers, JNIAccessibleClass declaringClass, + JNIJavaCallMethod javaCallMethod, JNIJavaCallWrapperMethod varargsCallWrapper, JNIJavaCallWrapperMethod arrayCallWrapper, JNIJavaCallWrapperMethod valistCallWrapper, @@ -96,12 +101,13 @@ public static ResolvedJavaField getCallWrapperField(MetaAccessProvider metaAcces JNIJavaCallWrapperMethod valistNonvirtualCallWrapperMethod) { super(declaringClass); - assert varargsCallWrapper != null && arrayCallWrapper != null && valistCallWrapper != null; + assert javaCallMethod != null && 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); this.descriptor = descriptor; this.modifiers = modifiers; + this.javaCallMethod = javaCallMethod; this.varargsCallWrapperMethod = varargsCallWrapper; this.arrayCallWrapperMethod = arrayCallWrapper; this.valistCallWrapperMethod = valistCallWrapper; @@ -110,11 +116,15 @@ public static ResolvedJavaField getCallWrapperField(MetaAccessProvider metaAcces this.valistNonvirtualCallWrapperMethod = valistNonvirtualCallWrapperMethod; } - public boolean isPublic() { + public CodePointer getJavaCallAddress() { + return javaCall; + } + + boolean isPublic() { return Modifier.isPublic(modifiers); } - public boolean isStatic() { + boolean isStatic() { return Modifier.isStatic(modifiers); } @@ -122,6 +132,7 @@ public boolean isStatic() { void finishBeforeCompilation(CompilationAccessImpl access) { HostedUniverse hUniverse = access.getUniverse(); AnalysisUniverse aUniverse = access.getUniverse().getBigBang().getUniverse(); + javaCall = new MethodPointer(hUniverse.lookup(aUniverse.lookup(javaCallMethod))); varargsCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(varargsCallWrapperMethod))); arrayCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(arrayCallWrapperMethod))); valistCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(valistCallWrapperMethod))); 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/JNIGraphKit.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIGraphKit.java index e4246586154f..e11a56360d13 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 @@ -83,23 +83,26 @@ public ValueNode checkObjectType(ValueNode uncheckedValue, ResolvedJavaType type return value; } - /** Masks a sub-long value to ensure that unused high bits are indeed cleared. */ - public ValueNode maskIntegerBits(ValueNode value, JavaKind kind) { + /** Masks bits to ensure that unused bytes in the stack representation are cleared. */ + public ValueNode maskNumericIntBytes(ValueNode value, JavaKind kind) { assert kind.isNumericInteger(); - if (kind == JavaKind.Long) { - return value; - } 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(narrowed, ConstantNode.forIntegerBits(bits, 0), NodeView.DEFAULT); - return append(ConditionalNode.create(isZero, ConstantNode.forBoolean(false), ConstantNode.forBoolean(true), NodeView.DEFAULT)); + 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(narrowed, stackBits, NodeView.DEFAULT)); + return append(ZeroExtendNode.create(value, stackBits, NodeView.DEFAULT)); } else { - return append(SignExtendNode.create(narrowed, stackBits, NodeView.DEFAULT)); + return append(SignExtendNode.create(value, stackBits, NodeView.DEFAULT)); } } @@ -146,6 +149,10 @@ public InvokeWithExceptionNode getFieldOffsetFromId(ValueNode fieldId) { return createStaticInvoke("getFieldOffsetFromId", fieldId); } + public InvokeWithExceptionNode getJavaCallAddressFromMethodId(ValueNode methodId) { + return createStaticInvoke("getJavaCallAddressFromMethodId", methodId); + } + public InvokeWithExceptionNode getStaticPrimitiveFieldsArray() { return createStaticInvoke("getStaticPrimitiveFieldsArray"); } 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 new file mode 100644 index 000000000000..ea38fbcb840a --- /dev/null +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallMethod.java @@ -0,0 +1,249 @@ +/* + * 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); + + 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/JNIJavaCallWrapperMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallWrapperMethod.java index 9e2b278c9177..1bb145b8f195 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 @@ -24,76 +24,54 @@ */ 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; import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind; 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.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.calc.IsNullNode; -import org.graalvm.compiler.nodes.calc.ObjectEqualsNode; -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.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 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.meta.HostedProviders; -import com.oracle.svm.core.SubstrateUtil; +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.CEntryPointLeaveNode.LeaveAction; import com.oracle.svm.core.graal.nodes.CInterfaceReadNode; -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.SimpleSignature; 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.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; @@ -103,10 +81,11 @@ 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. It delegates to a generated + * {@link JNIJavaCallMethod} for the actual call to a particular Java method. * * @see Java 8 JNI @@ -116,8 +95,8 @@ */ public class JNIJavaCallWrapperMethod extends EntryPointCallStubMethod { 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(JNICallSignature javaCallSignature, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider metaAccess) { + return new JNIJavaCallWrapperMethod(javaCallSignature, callVariant, nonVirtual, metaAccess); } } @@ -127,29 +106,25 @@ public enum CallVariant { VA_LIST, } - private final NativeLibraries nativeLibs; - - private final Executable reflectMethod; + private final Signature javaCallSignature; 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), + protected JNIJavaCallWrapperMethod(JNICallSignature javaCallSignature, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider metaAccess) { + super(createName(javaCallSignature, callVariant, nonVirtual), metaAccess.lookupJavaType(JNIJavaCallWrappers.class), - createSignature(reflectMethod, callVariant, nonVirtual, metaAccess), + createSignature(javaCallSignature, callVariant, nonVirtual, metaAccess), JNIJavaCallWrappers.getConstantPool(metaAccess)); - assert !nonVirtual || !Modifier.isStatic(reflectMethod.getModifiers()); - this.reflectMethod = reflectMethod; - this.nativeLibs = nativeLibs; + this.javaCallSignature = javaCallSignature; this.callVariant = callVariant; this.nonVirtual = nonVirtual; } - private static String createName(Executable reflectMethod, CallVariant callVariant, boolean nonVirtual) { - return "jniInvoke_" + callVariant.name() + (nonVirtual ? "_Nonvirtual" : "") + "_" + SubstrateUtil.uniqueShortName(reflectMethod); + private static String createName(JNICallSignature targetSignature, CallVariant callVariant, boolean nonVirtual) { + return "invoke" + targetSignature.getIdentifier() + "_" + callVariant.name() + (nonVirtual ? "_Nonvirtual" : ""); } - private static SimpleSignature createSignature(Executable reflectMethod, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider metaAccess) { + private static SimpleSignature createSignature(Signature targetSignature, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider metaAccess) { ResolvedJavaType objectHandle = metaAccess.lookupJavaType(JNIObjectHandle.class); List args = new ArrayList<>(); args.add(metaAccess.lookupJavaType(JNIEnvironment.class)); @@ -158,16 +133,16 @@ private static SimpleSignature createSignature(Executable reflectMethod, CallVar 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(); + int count = targetSignature.getParameterCount(false); + for (int i = 2; i < count; i++) { // skip non-virtual, receiver/class arguments + JavaKind kind = targetSignature.getParameterKind(i); if (kind.isObject()) { args.add(objectHandle); - } else if (kind == JavaKind.Float) { // C varargs promote float to double + } else if (kind == JavaKind.Float) { + // C varargs promote float to double (C99, 6.5.2.2-6) args.add(metaAccess.lookupJavaType(JavaKind.Double.toJavaClass())); - } else { // C varargs promote sub-words to int + } else { // C varargs promote sub-words to int (C99, 6.5.2.2-6) args.add(metaAccess.lookupJavaType(kind.getStackKind().toJavaClass())); } } @@ -179,8 +154,7 @@ private static SimpleSignature createSignature(Executable reflectMethod, CallVar throw VMError.shouldNotReachHere(); } JavaType returnType = targetSignature.getReturnType(null); - if (returnType.getJavaKind().isObject() || targetMethod.isConstructor()) { - // Constructor: returns `this` to implement NewObject + if (returnType.getJavaKind().isObject()) { returnType = objectHandle; } return new SimpleSignature(args, returnType); @@ -197,131 +171,37 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, 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; + Signature invokeSignature = javaCallSignature; + if (metaAccess.getWrapped() instanceof UniverseMetaAccess) { + invokeSignature = ((UniverseMetaAccess) metaAccess.getWrapped()).getUniverse().lookup( + invokeSignature, (WrappedJavaType) ((WrappedJavaMethod) method).getWrapped().getDeclaringClass()); } + invokeSignature = metaAccess.getUniverse().lookup(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); + int firstParamIndex = 2; + List loadedArgs = loadAndUnboxArguments(kit, providers, invokeSignature, firstParamIndex); - 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); + int slotIndex = metaAccess.lookupJavaType(JNIEnvironment.class).getJavaKind().getSlotCount(); + JavaKind receiverOrClassKind = metaAccess.lookupJavaType(JNIObjectHandle.class).getJavaKind(); + ValueNode receiverOrClassHandle = kit.loadLocal(slotIndex, receiverOrClassKind); + ValueNode receiverOrClass = kit.unboxHandle(receiverOrClassHandle); + slotIndex += receiverOrClassKind.getSlotCount(); + if (nonVirtual) { + slotIndex += receiverOrClassKind.getSlotCount(); } - JavaKind returnKind = (returnValue != null) ? returnValue.getStackKind() : JavaKind.Void; - - 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); + JavaKind methodIdKind = metaAccess.lookupJavaType(JNIMethodId.class).getJavaKind(); + ValueNode methodId = kit.loadLocal(slotIndex, methodIdKind); + ValueNode javaCallAddress = kit.getJavaCallAddressFromMethodId(methodId); + + ValueNode[] args = new ValueNode[2 + loadedArgs.size()]; + args[0] = kit.createInt(nonVirtual ? 1 : 0); + args[1] = receiverOrClass; + for (int i = 0; i < loadedArgs.size(); i++) { + args[2 + i] = loadedArgs.get(i); } + ValueNode returnValue = createMethodCall(kit, invokeSignature.toParameterTypes(null), invokeSignature.getReturnType(null), state, javaCallAddress, args); + JavaKind returnKind = (returnValue != null) ? returnValue.getStackKind() : JavaKind.Void; if (returnKind.isObject()) { returnValue = kit.boxObjectInLocalHandle(returnValue); } @@ -333,64 +213,24 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, 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 static final Constructor INSTANTIATION_EXCEPTION_CONSTRUCTOR = ReflectionUtil.lookupConstructor(InstantiationException.class); - - /** - * 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)); - } - /** * 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 + * or null/zero/false if 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) { + protected ValueNode createMethodCall(JNIGraphKit kit, JavaType[] paramTypes, JavaType returnType, FrameStateBuilder state, ValueNode address, ValueNode... args) { int bci = kit.bci(); - InvokeWithExceptionNode invoke = startInvokeWithRetainedException(kit, invokeMethod, invokeKind, state, bci, args); + InvokeWithExceptionNode invoke = startInvokeWithRetainedException(kit, paramTypes, returnType, state, bci, address, args); AbstractMergeNode invokeMerge = kit.endInvokeWithException(); - if (invoke.getStackKind() == JavaKind.Void && !invokeMethod.isConstructor()) { + if (invoke.getStackKind() == JavaKind.Void) { invokeMerge.setStateAfter(state.create(bci, invokeMerge)); return null; } - 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)); + 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)); @@ -398,9 +238,14 @@ protected ValueNode createMethodCall(JNIGraphKit kit, ResolvedJavaMethod invokeM return returnValue; } - protected static InvokeWithExceptionNode startInvokeWithRetainedException(JNIGraphKit kit, ResolvedJavaMethod invokeMethod, InvokeKind kind, FrameStateBuilder state, int bci, ValueNode... args) { + protected static InvokeWithExceptionNode startInvokeWithRetainedException(JNIGraphKit kit, JavaType[] paramTypes, + JavaType returnType, FrameStateBuilder state, int bci, ValueNode methodAddress, ValueNode... args) { ValueNode formerPendingException = kit.getAndClearPendingException(); - InvokeWithExceptionNode invoke = kit.startInvokeWithException(invokeMethod, kind, state, bci, args); + + 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); kit.noExceptionPart(); // no new exception was thrown, restore the formerly pending one kit.setPendingException(formerPendingException); @@ -412,29 +257,6 @@ protected static InvokeWithExceptionNode startInvokeWithRetainedException(JNIGra return invoke; } - 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); - 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())); - 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 @@ -442,87 +264,84 @@ private static PiNode typeChecked(JNIGraphKit kit, ValueNode uncheckedValue, Res * * @return List of created argument nodes and their type */ - private List> loadAndUnboxArguments(JNIGraphKit kit, HostedProviders providers, Signature invokeSignature) { + private List loadAndUnboxArguments(JNIGraphKit kit, HostedProviders providers, Signature invokeSignature, int firstParamIndex) { MetaAccessProvider metaAccess = providers.getMetaAccess(); - List> args = new ArrayList<>(); - int javaIndex = argumentsJavaIndex(metaAccess); + List args = new ArrayList<>(); + int slotIndex = firstArgumentSlotIndex(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) { + 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)) { 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()); + array = kit.loadLocal(slotIndex, 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)) { + for (int i = firstParamIndex; i < count; i++) { + JavaKind kind = invokeSignature.getParameterKind(i); + assert kind == kind.getStackKind() : "other conversions and bit masking than below must happen in JNIJavaCallMethod"; + JavaKind readKind = kind; + if (kind == JavaKind.Float && (callVariant == CallVariant.VARARGS || callVariant == CallVariant.VA_LIST)) { readKind = JavaKind.Double; + } else if (kind.isObject()) { + readKind = providers.getWordTypes().getWordKind(); } - StructFieldInfo fieldInfo = getJNIValueOffsetOf(elementType, readKind); - int offset = i * elementSize + fieldInfo.getOffsetInfo().getProperty(); + /* + * 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) * elementSize; 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)) { + 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)); } else if (kind.isObject()) { value = kit.unboxHandle(value); - } else if (kind == JavaKind.Boolean) { - value = kit.maskIntegerBits(value, JavaKind.Boolean); - } else if (kind != kind.getStackKind() && callVariant == CallVariant.ARRAY) { - value = kit.maskIntegerBits(value, kind); } - args.add(Pair.create(value, type)); + args.add(value); } } else if (callVariant == CallVariant.VARARGS) { - for (int i = 0; i < count; i++) { - ResolvedJavaType type = (ResolvedJavaType) invokeSignature.getParameterType(i, null); - JavaKind kind = type.getJavaKind(); + for (int i = firstParamIndex; i < count; i++) { + JavaKind kind = invokeSignature.getParameterKind(i); + assert kind == kind.getStackKind() : "other conversions and bit masking than below must happen in JNIJavaCallMethod"; JavaKind loadKind = kind; - if (loadKind == JavaKind.Float) { // C varargs promote float to double - loadKind = JavaKind.Double; + if (loadKind == JavaKind.Float) { + loadKind = JavaKind.Double; // C varargs promote float to double (C99 6.5.2.2-6) } - ValueNode value = kit.loadLocal(javaIndex, loadKind); + ValueNode value = kit.loadLocal(slotIndex, 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 = kit.maskIntegerBits(value, JavaKind.Boolean); } - args.add(Pair.create(value, type)); - javaIndex += loadKind.getSlotCount(); + args.add(value); + slotIndex += 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(); + ValueNode valist = kit.loadLocal(slotIndex, metaAccess.lookupJavaType(WordBase.class).getJavaKind()); + for (int i = firstParamIndex; i < count; i++) { + JavaKind kind = invokeSignature.getParameterKind(i); + assert kind == kind.getStackKind() : "other conversions and bit masking than below must happen in JNIJavaCallMethod"; + JavaKind loadKind = kind; 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 = kit.maskIntegerBits(value, JavaKind.Boolean); } - args.add(Pair.create(value, type)); + args.add(value); } } else { throw VMError.unsupportedFeature("Call variant: " + callVariant); @@ -530,39 +349,11 @@ private List> loadAndUnboxArguments(JNIGraphKi return args; } - /** - * Returns the index of the frame state local for the first argument. - */ - private int argumentsJavaIndex(MetaAccessProvider metaAccess) { + /** Returns the index of the frame state local for the first actual (non-receiver) argument. */ + private int firstArgumentSlotIndex(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(); } - - 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 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; - } - } - } - throw VMError.shouldNotReachHere(); - } } diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java index 6522330c5f71..8c8392cd1f1b 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java @@ -139,7 +139,7 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, // Just before return to always run the epilogue and never suppress a pending exception returnValue = kit.checkObjectType(returnValue, (ResolvedJavaType) returnType, false); } else if (returnKind != JavaKind.Void && JNINativeCallWrapperMethod.returnKindWidensToLong(returnKind)) { - returnValue = kit.maskIntegerBits(returnValue, returnKind); + returnValue = kit.maskNumericIntBytes(returnValue, returnKind); } kit.createReturn(returnValue, returnKind); return kit.finalizeGraph(); From 442fd889d5ea5c979ad29e3595932e58ef91aaa4 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 22 Jun 2022 18:14:39 +0200 Subject: [PATCH 03/11] Work around an issue with the LLVM compiler backend. --- .../src/com/oracle/svm/jni/JNIGeneratedMethodSupport.java | 4 ++++ .../src/com/oracle/svm/jni/access/JNIAccessibleMethod.java | 4 ++++ .../com/oracle/svm/jni/access/JNIReflectionDictionary.java | 2 ++ 3 files changed, 10 insertions(+) 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 8bb1e7c74434..d448aba9e670 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 @@ -33,6 +33,8 @@ import org.graalvm.word.WordFactory; import com.oracle.svm.core.StaticFieldsSupport; +import com.oracle.svm.core.annotate.AlwaysInline; +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; @@ -79,6 +81,8 @@ static WordBase getFieldOffsetFromId(JNIFieldId fieldId) { return JNIAccessibleField.getOffsetFromId(fieldId); } + @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) static CodePointer getJavaCallAddressFromMethodId(JNIMethodId methodId) { return JNIReflectionDictionary.getMethodByID(methodId).getJavaCallAddress(); } 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 c61bdbbeb26a..3cbe4c0a3251 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 @@ -32,6 +32,8 @@ import org.graalvm.nativeimage.c.function.CodePointer; 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.meta.MethodPointer; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl.CompilationAccessImpl; @@ -116,6 +118,8 @@ static ResolvedJavaField getCallWrapperField(MetaAccessProvider metaAccess, Call this.valistNonvirtualCallWrapperMethod = valistNonvirtualCallWrapperMethod; } + @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 getJavaCallAddress() { return javaCall; } 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..b99d7789a6e9 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,6 +246,7 @@ private static JNIMethodId toMethodID(JNIAccessibleMethod method) { return (JNIMethodId) value; } + @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) public static JNIAccessibleMethod getMethodByID(JNIMethodId method) { Object obj = null; if (method.notEqual(WordFactory.zero())) { From cb861b325c297e439bea78e7d307cf39dd691ab6 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 22 Jun 2022 18:58:38 +0200 Subject: [PATCH 04/11] Enable inlining for more JNI code called from native. --- .../src/com/oracle/svm/jni/JNIGeneratedMethodSupport.java | 6 ++++++ .../com/oracle/svm/jni/JNIThreadLocalPendingException.java | 2 ++ .../src/com/oracle/svm/jni/access/JNIAccessibleField.java | 2 ++ 3 files changed, 10 insertions(+) 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 d448aba9e670..54d260e31cbc 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 @@ -77,6 +77,7 @@ static Object unboxHandle(JNIObjectHandle handle) { return JNIObjectHandles.getObject(handle); } + @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) static WordBase getFieldOffsetFromId(JNIFieldId fieldId) { return JNIAccessibleField.getOffsetFromId(fieldId); } @@ -87,24 +88,29 @@ static CodePointer getJavaCallAddressFromMethodId(JNIMethodId methodId) { return JNIReflectionDictionary.getMethodByID(methodId).getJavaCallAddress(); } + @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) static Object getStaticPrimitiveFieldsArray() { return StaticFieldsSupport.getStaticPrimitiveFields(); } + @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) static Object getStaticObjectFieldsArray() { return StaticFieldsSupport.getStaticObjectFields(); } + @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) static void setPendingException(Throwable t) { JNIThreadLocalPendingException.set(t); } + @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) static Throwable getAndClearPendingException() { Throwable t = JNIThreadLocalPendingException.get(); JNIThreadLocalPendingException.clear(); return t; } + @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) static void rethrowPendingException() throws Throwable { Throwable t = getAndClearPendingException(); if (t != null) { diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIThreadLocalPendingException.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIThreadLocalPendingException.java index cdb37369b785..4c1c0767554a 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIThreadLocalPendingException.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIThreadLocalPendingException.java @@ -34,6 +34,7 @@ public class JNIThreadLocalPendingException { private static final FastThreadLocalObject pendingException = FastThreadLocalFactory.createObject(Throwable.class, "JNIThreadLocalPendingException.pendingException"); + @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) public static Throwable get() { return pendingException.get(); } @@ -43,6 +44,7 @@ public static void set(Throwable t) { pendingException.set(t); } + @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) public static void clear() { set(null); } 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..96e9bd50f276 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 call wrappers, which are uninterruptible.", mayBeInlined = true) public static WordBase getOffsetFromId(JNIFieldId id) { UnsignedWord result = ((UnsignedWord) id).and(ID_OFFSET_MASK); assert result.notEqual(0); From 7956e99d64a868ef20577d4276ce376c63733af3 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 23 Jun 2022 11:28:41 +0200 Subject: [PATCH 05/11] Allow inlining for JNI handle lookups and creation. --- .../svm/core/handles/ThreadLocalHandles.java | 17 +++++++- .../svm/jni/JNIGeneratedMethodSupport.java | 16 ++++---- .../com/oracle/svm/jni/JNIObjectHandles.java | 41 ++++++++++++++++++- .../jni/JNIThreadLocalPendingException.java | 4 +- .../svm/jni/access/JNIAccessibleField.java | 2 +- .../svm/jni/access/JNIAccessibleMethod.java | 2 +- .../jni/access/JNIReflectionDictionary.java | 2 +- 7 files changed, 69 insertions(+), 15 deletions(-) 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.jni/src/com/oracle/svm/jni/JNIGeneratedMethodSupport.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIGeneratedMethodSupport.java index 54d260e31cbc..2347bb0b18b1 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 @@ -69,48 +69,50 @@ 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 call wrappers, which are uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Allow inlining from field accessor methods, which are uninterruptible.", mayBeInlined = true) static WordBase getFieldOffsetFromId(JNIFieldId fieldId) { return JNIAccessibleField.getOffsetFromId(fieldId); } @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) + @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) static CodePointer getJavaCallAddressFromMethodId(JNIMethodId methodId) { return JNIReflectionDictionary.getMethodByID(methodId).getJavaCallAddress(); } - @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Allow inlining from field accessor methods, which are uninterruptible.", mayBeInlined = true) static Object getStaticPrimitiveFieldsArray() { return StaticFieldsSupport.getStaticPrimitiveFields(); } - @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Allow inlining from field accessor methods, which are uninterruptible.", mayBeInlined = true) static Object getStaticObjectFieldsArray() { return StaticFieldsSupport.getStaticObjectFields(); } - @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) static void setPendingException(Throwable t) { JNIThreadLocalPendingException.set(t); } - @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) static Throwable getAndClearPendingException() { Throwable t = JNIThreadLocalPendingException.get(); JNIThreadLocalPendingException.clear(); return t; } - @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) static void rethrowPendingException() throws Throwable { Throwable t = getAndClearPendingException(); if (t != null) { 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/JNIThreadLocalPendingException.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIThreadLocalPendingException.java index 4c1c0767554a..6a8d48ec39e1 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIThreadLocalPendingException.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIThreadLocalPendingException.java @@ -34,7 +34,7 @@ public class JNIThreadLocalPendingException { private static final FastThreadLocalObject pendingException = FastThreadLocalFactory.createObject(Throwable.class, "JNIThreadLocalPendingException.pendingException"); - @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) public static Throwable get() { return pendingException.get(); } @@ -44,7 +44,7 @@ public static void set(Throwable t) { pendingException.set(t); } - @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) public static void clear() { set(null); } 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 96e9bd50f276..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 @@ -65,7 +65,7 @@ public final class JNIAccessibleField extends JNIAccessibleMember { * {@link StaticFieldsSupport#getStaticPrimitiveFields()} or * {@link StaticFieldsSupport#getStaticObjectFields()}. */ - @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) + @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 3cbe4c0a3251..ad2b00d68566 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 @@ -119,7 +119,7 @@ static ResolvedJavaField getCallWrapperField(MetaAccessProvider metaAccess, Call } @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) + @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) public CodePointer getJavaCallAddress() { return javaCall; } 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 b99d7789a6e9..1e4ad561e2fc 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,7 +246,7 @@ private static JNIMethodId toMethodID(JNIAccessibleMethod method) { return (JNIMethodId) value; } - @Uninterruptible(reason = "Allow inlining from call wrappers, which are uninterruptible.", mayBeInlined = true) + @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())) { From dda9147d069976ee4037d1cdec2b6343400378c8 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 28 Jun 2022 13:26:48 +0200 Subject: [PATCH 06/11] Split Java call wrappers into call variant and call wrappers. --- .../svm/jni/JNIGeneratedMethodSupport.java | 9 +- .../svm/jni/JNIJavaCallTrampolines.java | 2 +- .../svm/jni/JNIJavaCallVariantWrappers.java | 44 +++ .../jni/JNIThreadLocalPendingException.java | 2 - .../svm/jni/access/JNIAccessFeature.java | 61 ++-- .../svm/jni/access/JNIAccessibleMethod.java | 92 +++--- .../jni/access/JNIReflectionDictionary.java | 2 +- .../functions/JNIFunctionTablesFeature.java | 4 +- .../jni/hosted/JNICallTrampolineMethod.java | 16 +- .../oracle/svm/jni/hosted/JNIGraphKit.java | 4 + .../svm/jni/hosted/JNIJavaCallMethod.java | 2 +- .../JNIJavaCallVariantWrapperMethod.java | 274 ++++++++++++++++++ .../jni/hosted/JNIJavaCallWrapperMethod.java | 263 ++++------------- 13 files changed, 480 insertions(+), 295 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCallVariantWrappers.java create mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallVariantWrapperMethod.java 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 2347bb0b18b1..dff242f9b03c 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 @@ -84,6 +84,12 @@ static WordBase getFieldOffsetFromId(JNIFieldId fieldId) { return JNIAccessibleField.getOffsetFromId(fieldId); } + @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) + static CodePointer getJavaCallWrapperAddressFromMethodId(JNIMethodId methodId) { + return JNIReflectionDictionary.getMethodByID(methodId).getCallWrapperAddress(); + } + @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) static CodePointer getJavaCallAddressFromMethodId(JNIMethodId methodId) { @@ -100,19 +106,16 @@ static Object getStaticObjectFieldsArray() { return StaticFieldsSupport.getStaticObjectFields(); } - @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) static void setPendingException(Throwable t) { JNIThreadLocalPendingException.set(t); } - @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) static Throwable getAndClearPendingException() { Throwable t = JNIThreadLocalPendingException.get(); JNIThreadLocalPendingException.clear(); return t; } - @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) static void rethrowPendingException() throws Throwable { Throwable t = getAndClearPendingException(); if (t != null) { 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/JNIThreadLocalPendingException.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIThreadLocalPendingException.java index 6a8d48ec39e1..cdb37369b785 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIThreadLocalPendingException.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIThreadLocalPendingException.java @@ -34,7 +34,6 @@ public class JNIThreadLocalPendingException { private static final FastThreadLocalObject pendingException = FastThreadLocalFactory.createObject(Throwable.class, "JNIThreadLocalPendingException.pendingException"); - @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) public static Throwable get() { return pendingException.get(); } @@ -44,7 +43,6 @@ public static void set(Throwable t) { pendingException.set(t); } - @Uninterruptible(reason = "Allow inlining from entry points, which are uninterruptible.", mayBeInlined = true) public static void clear() { set(null); } 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 5bf7e1b09db6..e073aa13d035 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 @@ -71,8 +71,9 @@ 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; -import com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod.CallVariant; import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.MetaAccessProvider; @@ -90,14 +91,14 @@ public static JNIAccessFeature singleton() { return ImageSingletons.lookup(JNIAccessFeature.class); } - static final class JNIJavaCallWrapperGroup { - static final JNIJavaCallWrapperGroup NONE = new JNIJavaCallWrapperGroup(null, null, null); + static final class JNIJavaCallVariantWrapperGroup { + static final JNIJavaCallVariantWrapperGroup NONE = new JNIJavaCallVariantWrapperGroup(null, null, null); - final JNIJavaCallWrapperMethod varargs; - final JNIJavaCallWrapperMethod array; - final JNIJavaCallWrapperMethod valist; + final JNIJavaCallVariantWrapperMethod varargs; + final JNIJavaCallVariantWrapperMethod array; + final JNIJavaCallVariantWrapperMethod valist; - JNIJavaCallWrapperGroup(JNIJavaCallWrapperMethod varargs, JNIJavaCallWrapperMethod array, JNIJavaCallWrapperMethod valist) { + JNIJavaCallVariantWrapperGroup(JNIJavaCallVariantWrapperMethod varargs, JNIJavaCallVariantWrapperMethod array, JNIJavaCallVariantWrapperMethod valist) { this.varargs = varargs; this.array = array; this.valist = valist; @@ -106,8 +107,9 @@ static final class JNIJavaCallWrapperGroup { private boolean sealed = false; private final Map trampolineMethods = new ConcurrentHashMap<>(); - private final Map callWrappers = new ConcurrentHashMap<>(); - private final Map nonvirtualCallWrappers = new ConcurrentHashMap<>(); + private final Map javaCallWrapperMethods = new ConcurrentHashMap<>(); + private final Map callVariantWrappers = new ConcurrentHashMap<>(); + private final Map nonvirtualCallVariantWrappers = new ConcurrentHashMap<>(); private int loadedConfigurations; @@ -176,9 +178,6 @@ public void beforeAnalysis(BeforeAnalysisAccess arg) { if (!ImageSingletons.contains(JNIFieldAccessorMethod.Factory.class)) { ImageSingletons.add(JNIFieldAccessorMethod.Factory.class, new JNIFieldAccessorMethod.Factory()); } - if (!ImageSingletons.contains(JNIJavaCallWrapperMethod.Factory.class)) { - ImageSingletons.add(JNIJavaCallWrapperMethod.Factory.class, new JNIJavaCallWrapperMethod.Factory()); - } if (!ImageSingletons.contains(JNIJavaCallMethod.Factory.class)) { ImageSingletons.add(JNIJavaCallMethod.Factory.class, new JNIJavaCallMethod.Factory()); } @@ -200,7 +199,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); @@ -223,7 +222,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); }); @@ -309,33 +308,37 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) { JNIJavaCallMethod javaCallMethod = ImageSingletons.lookup(JNIJavaCallMethod.Factory.class).create(targetMethod, universe, wordTypes); access.registerAsRoot(universe.lookup(javaCallMethod), true); - JNICallSignature javaCallSignature = javaCallMethod.getSignature(); - JNIJavaCallWrapperGroup wrappers = createJavaCallWrappers(access, javaCallSignature, false); - JNIJavaCallWrapperGroup nonvirtualWrappers = JNIJavaCallWrapperGroup.NONE; + JNIJavaCallWrapperMethod callWrapperMethod = javaCallWrapperMethods.computeIfAbsent(javaCallMethod.getSignature(), + signature -> new JNIJavaCallWrapperMethod(signature, universe.getOriginalMetaAccess(), wordTypes)); + access.registerAsRoot(universe.lookup(callWrapperMethod), true); + + JNIJavaCallVariantWrapperGroup variantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), false); + JNIJavaCallVariantWrapperGroup nonvirtualVariantWrappers = JNIJavaCallVariantWrapperGroup.NONE; if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isAbstract(method.getModifiers())) { - nonvirtualWrappers = createJavaCallWrappers(access, javaCallSignature, true); + nonvirtualVariantWrappers = createJavaCallVariantWrappers(access, callWrapperMethod.getSignature(), true); } - return new JNIAccessibleMethod(d, method.getModifiers(), jniClass, javaCallMethod, wrappers.varargs, wrappers.array, wrappers.valist, - nonvirtualWrappers.varargs, nonvirtualWrappers.array, nonvirtualWrappers.valist); + return new JNIAccessibleMethod(d, method.getModifiers(), jniClass, javaCallMethod, callWrapperMethod, + variantWrappers.varargs, variantWrappers.array, variantWrappers.valist, + nonvirtualVariantWrappers.varargs, nonvirtualVariantWrappers.array, nonvirtualVariantWrappers.valist); }); } - private JNIJavaCallWrapperGroup createJavaCallWrappers(DuringAnalysisAccessImpl access, JNICallSignature javaCallSignature, boolean nonVirtual) { - var map = nonVirtual ? nonvirtualCallWrappers : callWrappers; - return map.computeIfAbsent(javaCallSignature, signature -> { + private JNIJavaCallVariantWrapperGroup createJavaCallVariantWrappers(DuringAnalysisAccessImpl access, JNICallSignature wrapperSignature, boolean nonVirtual) { + var map = nonVirtual ? nonvirtualCallVariantWrappers : callVariantWrappers; + return map.computeIfAbsent(wrapperSignature, signature -> { MetaAccessProvider originalMetaAccess = access.getUniverse().getOriginalMetaAccess(); - JNIJavaCallWrapperMethod.Factory factory = ImageSingletons.lookup(JNIJavaCallWrapperMethod.Factory.class); - JNIJavaCallWrapperMethod varargs = factory.create(signature, CallVariant.VARARGS, nonVirtual, originalMetaAccess); - JNIJavaCallWrapperMethod array = factory.create(signature, CallVariant.ARRAY, nonVirtual, originalMetaAccess); - JNIJavaCallWrapperMethod valist = factory.create(signature, CallVariant.VA_LIST, nonVirtual, originalMetaAccess); - Stream wrappers = Stream.of(varargs, array, valist); + 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 new JNIJavaCallWrapperGroup(varargs, array, valist); + return new JNIJavaCallVariantWrapperGroup(varargs, array, valist); }); } 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 ad2b00d68566..f38f196d821b 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 @@ -39,8 +39,10 @@ import com.oracle.svm.hosted.FeatureImpl.CompilationAccessImpl; 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; -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; @@ -52,7 +54,7 @@ */ public final class JNIAccessibleMethod extends JNIAccessibleMember { - 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"); @@ -66,56 +68,55 @@ static ResolvedJavaField getCallWrapperField(MetaAccessProvider metaAccess, Call 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; private CodePointer javaCall; - @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; + 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 JNIJavaCallMethod javaCallMethod; - @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; + @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, JNIJavaCallMethod javaCallMethod, - JNIJavaCallWrapperMethod varargsCallWrapper, - JNIJavaCallWrapperMethod arrayCallWrapper, - JNIJavaCallWrapperMethod valistCallWrapper, - JNIJavaCallWrapperMethod varargsNonvirtualCallWrapperMethod, - JNIJavaCallWrapperMethod arrayNonvirtualCallWrapperMethod, - JNIJavaCallWrapperMethod valistNonvirtualCallWrapperMethod) { + JNIJavaCallWrapperMethod callWrapperMethod, + JNIJavaCallVariantWrapperMethod varargsWrapper, + JNIJavaCallVariantWrapperMethod arrayWrapper, + JNIJavaCallVariantWrapperMethod valistWrapper, + JNIJavaCallVariantWrapperMethod varargsNonvirtualWrapper, + JNIJavaCallVariantWrapperMethod arrayNonvirtualWrapper, + JNIJavaCallVariantWrapperMethod valistNonvirtualWrapper) { super(declaringClass); - - assert javaCallMethod != null && varargsCallWrapper != null && arrayCallWrapper != null && valistCallWrapper != null; + assert javaCallMethod != null && callWrapperMethod != null && varargsWrapper != null && arrayWrapper != null && valistWrapper != null; assert (Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers)) // - ? (varargsNonvirtualCallWrapperMethod == null && arrayNonvirtualCallWrapperMethod == null && valistNonvirtualCallWrapperMethod == null) - : (varargsNonvirtualCallWrapperMethod != null & arrayNonvirtualCallWrapperMethod != null && valistNonvirtualCallWrapperMethod != null); + ? (varargsNonvirtualWrapper == null && arrayNonvirtualWrapper == null && valistNonvirtualWrapper == null) + : (varargsNonvirtualWrapper != null & arrayNonvirtualWrapper != null && valistNonvirtualWrapper != null); this.descriptor = descriptor; this.modifiers = modifiers; this.javaCallMethod = javaCallMethod; - this.varargsCallWrapperMethod = varargsCallWrapper; - this.arrayCallWrapperMethod = arrayCallWrapper; - this.valistCallWrapperMethod = valistCallWrapper; - this.varargsNonvirtualCallWrapperMethod = varargsNonvirtualCallWrapperMethod; - this.arrayNonvirtualCallWrapperMethod = arrayNonvirtualCallWrapperMethod; - this.valistNonvirtualCallWrapperMethod = valistNonvirtualCallWrapperMethod; + 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.") @@ -124,6 +125,12 @@ 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; + } + boolean isPublic() { return Modifier.isPublic(modifiers); } @@ -137,13 +144,14 @@ void finishBeforeCompilation(CompilationAccessImpl access) { HostedUniverse hUniverse = access.getUniverse(); AnalysisUniverse aUniverse = access.getUniverse().getBigBang().getUniverse(); javaCall = new MethodPointer(hUniverse.lookup(aUniverse.lookup(javaCallMethod))); - varargsCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(varargsCallWrapperMethod))); - arrayCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(arrayCallWrapperMethod))); - valistCallWrapper = new MethodPointer(hUniverse.lookup(aUniverse.lookup(valistCallWrapperMethod))); + 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 1e4ad561e2fc..1f73909ca4b7 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 @@ -254,7 +254,7 @@ public static JNIAccessibleMethod getMethodByID(JNIMethodId method) { if (SubstrateOptions.SpawnIsolates.getValue()) { p = p.add((UnsignedWord) Isolates.getHeapBase(CurrentIsolate.getIsolate())); } - obj = p.toObject(); + obj = p.toObjectNonNull(); } return (JNIAccessibleMethod) obj; } 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/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/JNIGraphKit.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIGraphKit.java index e11a56360d13..cd90e047ea28 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 @@ -149,6 +149,10 @@ public InvokeWithExceptionNode getFieldOffsetFromId(ValueNode fieldId) { return createStaticInvoke("getFieldOffsetFromId", fieldId); } + public InvokeWithExceptionNode getJavaCallWrapperAddressFromMethodId(ValueNode methodId) { + return createStaticInvoke("getJavaCallWrapperAddressFromMethodId", methodId); + } + public InvokeWithExceptionNode getJavaCallAddressFromMethodId(ValueNode methodId) { return createStaticInvoke("getJavaCallAddressFromMethodId", methodId); } 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 index ea38fbcb840a..67c24bfb6fce 100644 --- 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 @@ -78,7 +78,7 @@ public JNIJavaCallMethod create(ResolvedJavaMethod method, AnalysisUniverse anal private static final Constructor INSTANTIATION_EXCEPTION_CONSTRUCTOR = ReflectionUtil.lookupConstructor(InstantiationException.class); - static JNICallSignature getSignatureForTarget(ResolvedJavaMethod targetMethod, AnalysisUniverse analysisUniverse, WordTypes wordTypes) { + 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]; 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..e0e9d2fb2bec --- /dev/null +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallVariantWrapperMethod.java @@ -0,0 +1,274 @@ +/* + * 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.WrappedJavaMethod; +import com.oracle.graal.pointsto.infrastructure.WrappedJavaType; +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.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; + +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(JNICallSignature 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(JNICallSignature 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 new JNICallSignature(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); + + Signature invokeSignature = callWrapperSignature; + if (metaAccess.getWrapped() instanceof UniverseMetaAccess) { + invokeSignature = ((UniverseMetaAccess) metaAccess.getWrapped()).getUniverse().lookup( + invokeSignature, (WrappedJavaType) ((WrappedJavaMethod) method).getWrapped().getDeclaringClass()); + } + invokeSignature = metaAccess.getUniverse().lookup(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)); + + 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); + 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() : "other conversions and bit masking must happen in JNIJavaCallMethod"; + 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() : "other conversions and bit masking must happen in JNIJavaCallMethod"; + 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() : "other conversions and bit masking must happen in JNIJavaCallMethod"; + 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 1bb145b8f195..1c6aadb8b97d 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,11 +24,6 @@ */ 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; @@ -43,41 +38,21 @@ 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.java.ExceptionObjectNode; -import org.graalvm.compiler.nodes.memory.OnHeapMemoryAccess.BarrierType; -import org.graalvm.compiler.nodes.memory.address.OffsetAddressNode; -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.WrappedJavaMethod; import com.oracle.graal.pointsto.infrastructure.WrappedJavaType; 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.CEntryPointLeaveNode.LeaveAction; -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.code.NonBytecodeStaticMethod; 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 jdk.vm.ci.meta.JavaConstant; 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; /** @@ -93,83 +68,45 @@ * @see Java 11 * JNI functions documentation */ -public class JNIJavaCallWrapperMethod extends EntryPointCallStubMethod { - public static class Factory { - public JNIJavaCallWrapperMethod create(JNICallSignature javaCallSignature, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider metaAccess) { - return new JNIJavaCallWrapperMethod(javaCallSignature, callVariant, nonVirtual, metaAccess); - } - } - - public enum CallVariant { - VARARGS, - ARRAY, - VA_LIST, - } - +public class JNIJavaCallWrapperMethod extends NonBytecodeStaticMethod { private final Signature javaCallSignature; - private final CallVariant callVariant; - private final boolean nonVirtual; - protected JNIJavaCallWrapperMethod(JNICallSignature javaCallSignature, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider metaAccess) { - super(createName(javaCallSignature, callVariant, nonVirtual), - metaAccess.lookupJavaType(JNIJavaCallWrappers.class), - createSignature(javaCallSignature, callVariant, nonVirtual, metaAccess), - JNIJavaCallWrappers.getConstantPool(metaAccess)); + 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; - this.callVariant = callVariant; - this.nonVirtual = nonVirtual; - } - - private static String createName(JNICallSignature targetSignature, CallVariant callVariant, boolean nonVirtual) { - return "invoke" + targetSignature.getIdentifier() + "_" + callVariant.name() + (nonVirtual ? "_Nonvirtual" : ""); } - private static SimpleSignature createSignature(Signature targetSignature, 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)); - if (callVariant == CallVariant.VARARGS) { - int count = targetSignature.getParameterCount(false); - for (int i = 2; i < count; i++) { // skip non-virtual, receiver/class arguments - JavaKind kind = targetSignature.getParameterKind(i); - if (kind.isObject()) { - args.add(objectHandle); - } else if (kind == JavaKind.Float) { - // C varargs promote float to double (C99, 6.5.2.2-6) - args.add(metaAccess.lookupJavaType(JavaKind.Double.toJavaClass())); - } else { // C varargs promote sub-words to int (C99, 6.5.2.2-6) - args.add(metaAccess.lookupJavaType(kind.getStackKind().toJavaClass())); - } + 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]; + 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 + 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(); + args[3 + (i - 2)] = kind.getStackKind(); } - JavaType returnType = targetSignature.getReturnType(null); - if (returnType.getJavaKind().isObject()) { - returnType = objectHandle; + JavaKind returnKind = targetSignature.getReturnKind(); + if (returnKind.isObject()) { + returnKind = wordKind; // handle } - return new SimpleSignature(args, returnType); + return new JNICallSignature(args, returnKind, originalMetaAccess); + } + + @Override + public JNICallSignature getSignature() { + return (JNICallSignature) 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)); Signature invokeSignature = javaCallSignature; if (metaAccess.getWrapped() instanceof UniverseMetaAccess) { @@ -178,38 +115,31 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, } invokeSignature = metaAccess.getUniverse().lookup(invokeSignature, (WrappedJavaType) method.getDeclaringClass()); - int firstParamIndex = 2; - List loadedArgs = loadAndUnboxArguments(kit, providers, invokeSignature, firstParamIndex); - - int slotIndex = metaAccess.lookupJavaType(JNIEnvironment.class).getJavaKind().getSlotCount(); - JavaKind receiverOrClassKind = metaAccess.lookupJavaType(JNIObjectHandle.class).getJavaKind(); - ValueNode receiverOrClassHandle = kit.loadLocal(slotIndex, receiverOrClassKind); + JavaKind wordKind = providers.getWordTypes().getWordKind(); + int slotIndex = 0; + ValueNode receiverOrClassHandle = kit.loadLocal(slotIndex, wordKind); ValueNode receiverOrClass = kit.unboxHandle(receiverOrClassHandle); - slotIndex += receiverOrClassKind.getSlotCount(); - if (nonVirtual) { - slotIndex += receiverOrClassKind.getSlotCount(); - } - JavaKind methodIdKind = metaAccess.lookupJavaType(JNIMethodId.class).getJavaKind(); - ValueNode methodId = kit.loadLocal(slotIndex, methodIdKind); - ValueNode javaCallAddress = kit.getJavaCallAddressFromMethodId(methodId); + 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(); + + int firstParamIndex = 2; + ValueNode[] loadedArgs = loadAndUnboxArguments(kit, providers, invokeSignature, firstParamIndex, slotIndex); - ValueNode[] args = new ValueNode[2 + loadedArgs.size()]; - args[0] = kit.createInt(nonVirtual ? 1 : 0); + ValueNode[] args = new ValueNode[2 + loadedArgs.length]; + args[0] = nonVirtual; args[1] = receiverOrClass; - for (int i = 0; i < loadedArgs.size(); i++) { - args[2 + i] = loadedArgs.get(i); - } + System.arraycopy(loadedArgs, 0, args, 2, loadedArgs.length); - ValueNode returnValue = createMethodCall(kit, invokeSignature.toParameterTypes(null), invokeSignature.getReturnType(null), state, javaCallAddress, args); + 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; if (returnKind.isObject()) { returnValue = kit.boxObjectInLocalHandle(returnValue); } - - CEntryPointLeaveNode leave = new CEntryPointLeaveNode(LeaveAction.Leave); - kit.append(leave); kit.createReturn(returnValue, returnKind); - return kit.finalizeGraph(); } @@ -257,103 +187,24 @@ protected static InvokeWithExceptionNode startInvokeWithRetainedException(JNIGra return invoke; } - /** - * 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, int firstParamIndex) { - MetaAccessProvider metaAccess = providers.getMetaAccess(); - List args = new ArrayList<>(); - int slotIndex = firstArgumentSlotIndex(metaAccess); + private static ValueNode[] loadAndUnboxArguments(JNIGraphKit kit, HostedProviders providers, Signature invokeSignature, int firstParamIndex, int firstSlotIndex) { + 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)) { - 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(slotIndex, elementType.getJavaKind()); - } - for (int i = firstParamIndex; i < count; i++) { - JavaKind kind = invokeSignature.getParameterKind(i); - assert kind == kind.getStackKind() : "other conversions and bit masking than below must happen in JNIJavaCallMethod"; - JavaKind readKind = kind; - if (kind == JavaKind.Float && (callVariant == CallVariant.VARARGS || callVariant == CallVariant.VA_LIST)) { - readKind = JavaKind.Double; - } else if (kind.isObject()) { - readKind = providers.getWordTypes().getWordKind(); - } - /* - * 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) * elementSize; - JavaKind wordKind = providers.getWordTypes().getWordKind(); - 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)); - } else if (kind.isObject()) { - value = kit.unboxHandle(value); - } - args.add(value); + ValueNode[] args = new ValueNode[count - firstParamIndex]; + 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"; + JavaKind loadKind = kind; + if (kind.isObject()) { + loadKind = providers.getWordTypes().getWordKind(); } - } 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 than below must happen in JNIJavaCallMethod"; - 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)); - } else if (kind.isObject()) { - value = kit.unboxHandle(value); - } - args.add(value); - slotIndex += loadKind.getSlotCount(); + ValueNode value = kit.loadLocal(slotIndex, loadKind); + if (kind.isObject()) { + value = kit.unboxHandle(value); } - } else if (callVariant == CallVariant.VA_LIST) { - ValueNode valist = kit.loadLocal(slotIndex, metaAccess.lookupJavaType(WordBase.class).getJavaKind()); - for (int i = firstParamIndex; i < count; i++) { - JavaKind kind = invokeSignature.getParameterKind(i); - assert kind == kind.getStackKind() : "other conversions and bit masking than below must happen in JNIJavaCallMethod"; - JavaKind loadKind = kind; - if (loadKind.isObject()) { - loadKind = providers.getWordTypes().getWordKind(); - } - ValueNode value = kit.append(new VaListNextArgNode(loadKind, valist)); - if (kind.isObject()) { - value = kit.unboxHandle(value); - } - args.add(value); - } - } else { - throw VMError.unsupportedFeature("Call variant: " + callVariant); + args[i] = value; + slotIndex += loadKind.getSlotCount(); } return args; } - - /** Returns the index of the frame state local for the first actual (non-receiver) argument. */ - private int firstArgumentSlotIndex(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(); - } } From 41b91990812d972fb49ec6b1a708f2f4135e45f2 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 29 Jun 2022 09:18:47 +0200 Subject: [PATCH 07/11] Minor optimizations. --- .../svm/jni/JNIGeneratedMethodSupport.java | 16 --------------- .../jni/access/JNIReflectionDictionary.java | 18 ++++++++--------- .../oracle/svm/jni/hosted/JNIGraphKit.java | 20 +++++++++++++++++-- 3 files changed, 27 insertions(+), 27 deletions(-) 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 dff242f9b03c..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 @@ -26,22 +26,18 @@ import java.lang.reflect.Array; -import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.word.PointerBase; import org.graalvm.word.WordBase; import org.graalvm.word.WordFactory; import com.oracle.svm.core.StaticFieldsSupport; -import com.oracle.svm.core.annotate.AlwaysInline; 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; -import com.oracle.svm.jni.access.JNIReflectionDictionary; import com.oracle.svm.jni.nativeapi.JNIEnvironment; import com.oracle.svm.jni.nativeapi.JNIFieldId; -import com.oracle.svm.jni.nativeapi.JNIMethodId; import com.oracle.svm.jni.nativeapi.JNIObjectHandle; import jdk.internal.misc.Unsafe; @@ -84,18 +80,6 @@ static WordBase getFieldOffsetFromId(JNIFieldId fieldId) { return JNIAccessibleField.getOffsetFromId(fieldId); } - @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) - static CodePointer getJavaCallWrapperAddressFromMethodId(JNIMethodId methodId) { - return JNIReflectionDictionary.getMethodByID(methodId).getCallWrapperAddress(); - } - - @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) - static CodePointer getJavaCallAddressFromMethodId(JNIMethodId methodId) { - return JNIReflectionDictionary.getMethodByID(methodId).getJavaCallAddress(); - } - @Uninterruptible(reason = "Allow inlining from field accessor methods, which are uninterruptible.", mayBeInlined = true) static Object getStaticPrimitiveFieldsArray() { return StaticFieldsSupport.getStaticPrimitiveFields(); 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 1f73909ca4b7..b619632625b6 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,17 +246,17 @@ 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.toObjectNonNull(); + 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/hosted/JNIGraphKit.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIGraphKit.java index cd90e047ea28..5c02be3a190c 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,6 +24,7 @@ */ 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; @@ -49,6 +50,9 @@ 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; @@ -150,11 +154,23 @@ public InvokeWithExceptionNode getFieldOffsetFromId(ValueNode fieldId) { } public InvokeWithExceptionNode getJavaCallWrapperAddressFromMethodId(ValueNode methodId) { - return createStaticInvoke("getJavaCallWrapperAddressFromMethodId", methodId); + return invokeJNIMethodMethod(methodId, "getCallWrapperAddress"); } public InvokeWithExceptionNode getJavaCallAddressFromMethodId(ValueNode methodId) { - return createStaticInvoke("getJavaCallAddressFromMethodId", methodId); + return invokeJNIMethodMethod(methodId, "getJavaCallAddress"); + } + + /** + * 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) { + 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); } public InvokeWithExceptionNode getStaticPrimitiveFieldsArray() { From dbef3c674fb42bedb4eb6eadf8ca30d7f6ff8769 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 30 Jun 2022 15:51:13 +0200 Subject: [PATCH 08/11] 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 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/JNIJavaCalls.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNIJavaCalls.java deleted file mode 100644 index 3ee5b3ea12bc..000000000000 --- 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 e073aa13d035..cc20bd5d1a54 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 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/JNIAccessibleMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleMethod.java index f38f196d821b..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,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 b619632625b6..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 @@ -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 722abc7e2fa0..65b6b6ec0dbe 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 5c02be3a190c..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 @@ -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 67c24bfb6fce..000000000000 --- 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 e0e9d2fb2bec..db1d7029896c 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 1c6aadb8b97d..988f111a0279 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(); From ea3119b008f2c4d1b8a0940a1d7f8591f16d2fd5 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 7 Jul 2022 17:30:16 +0200 Subject: [PATCH 09/11] Revert sharing JNINativeCallWrapperMethods, it has no clear image size advantage. This reverts commit 744819a069aafc553b8ccff4e7af0b59a2a032f0. --- .../oracle/svm/jni/JNINativeCallWrappers.java | 42 ----- .../svm/jni/hosted/JNICallWrapperFeature.java | 16 +- .../svm/jni/hosted/JNINativeCallMethod.java | 156 ------------------ .../hosted/JNINativeCallWrapperMethod.java | 130 ++++++++++----- ...tiveCallWrapperSubstitutionProcessor.java} | 38 ++--- 5 files changed, 109 insertions(+), 273 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNINativeCallWrappers.java delete mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java rename substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/{JNINativeCallSubstitutionProcessor.java => JNINativeCallWrapperSubstitutionProcessor.java} (59%) diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNINativeCallWrappers.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNINativeCallWrappers.java deleted file mode 100644 index a668c202f629..000000000000 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/JNINativeCallWrappers.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 {@code JNINativeCallWrapperMethod}s. */ -public final class JNINativeCallWrappers { - /** - * 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(JNINativeCallWrappers.class).getDeclaredConstructors()[0].getConstantPool(); - } - - private JNINativeCallWrappers() { - } -} 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 e434ef927d00..3e70c9182d3f 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,25 +27,23 @@ import java.util.Arrays; import java.util.List; -import org.graalvm.nativeimage.c.function.CFunction; import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.svm.core.jni.JNIRuntimeAccess; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.jni.access.JNIAccessFeature; import com.oracle.svm.jni.access.JNIAccessibleMethod; -import com.oracle.svm.jni.nativeapi.JNINativeMethod; /** * Responsible for generating JNI call wrappers for Java-to-native and native-to-Java invocations. * *

- * Java-to-native call wrappers are created by {@link JNINativeCallSubstitutionProcessor}. It - * creates a {@link JNINativeMethod} for each reachable Java method with the {@code native} keyword - * (except those handled by other mechanisms such as {@link CFunction}). This method then invokes a - * {@link JNINativeCallWrapperMethod} that matches the native method's signature, which is shared - * between all native methods with that signature and that produces a graph that performs the native - * code invocation and is visible to the analysis. + * Java-to-native call wrappers are created by {@link JNINativeCallWrapperSubstitutionProcessor}. It + * creates a {@link JNINativeCallWrapperMethod} for each Java method that is declared with the + * {@code native} keyword and that was registered via {@link JNIRuntimeAccess} to be accessible via + * JNI at runtime. The method provides a graph that performs the native code invocation. This graph + * is visible to the analysis. *

* *

@@ -76,6 +74,6 @@ public List> getRequiredFeatures() { @Override public void duringSetup(DuringSetupAccess arg) { DuringSetupAccessImpl access = (DuringSetupAccessImpl) arg; - access.registerNativeSubstitutionProcessor(new JNINativeCallSubstitutionProcessor(access)); + access.registerNativeSubstitutionProcessor(new JNINativeCallWrapperSubstitutionProcessor(access)); } } diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java deleted file mode 100644 index 8c8392cd1f1b..000000000000 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallMethod.java +++ /dev/null @@ -1,156 +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.Field; -import java.lang.reflect.Modifier; -import java.util.List; - -import org.graalvm.compiler.debug.DebugContext; -import org.graalvm.compiler.java.FrameStateBuilder; -import org.graalvm.compiler.nodes.CallTargetNode; -import org.graalvm.compiler.nodes.ConstantNode; -import org.graalvm.compiler.nodes.StructuredGraph; -import org.graalvm.compiler.nodes.UnwindNode; -import org.graalvm.compiler.nodes.ValueNode; -import org.graalvm.compiler.nodes.java.MonitorEnterNode; -import org.graalvm.compiler.nodes.java.MonitorExitNode; -import org.graalvm.compiler.nodes.java.MonitorIdNode; - -import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess; -import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; -import com.oracle.graal.pointsto.meta.HostedProviders; -import com.oracle.svm.core.graal.nodes.CGlobalDataLoadAddressNode; -import com.oracle.svm.hosted.annotation.CustomSubstitutionMethod; -import com.oracle.svm.hosted.heap.SVMImageHeapScanner; -import com.oracle.svm.jni.access.JNIAccessFeature; -import com.oracle.svm.jni.access.JNINativeLinkage; -import com.oracle.svm.util.ReflectionUtil; - -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; - -/** - * Generated code for calling a specific native method from Java code by finding its entry point and - * delegating to a fitting {@link JNINativeCallWrapperMethod}. - */ -class JNINativeCallMethod extends CustomSubstitutionMethod { - private final Field linkageBuiltInAddressField = ReflectionUtil.lookupField(JNINativeLinkage.class, "builtInAddress"); - - private final JNINativeLinkage linkage; - private final JNINativeCallWrapperMethod wrapperMethod; - - JNINativeCallMethod(ResolvedJavaMethod method, JNINativeCallWrapperMethod wrapperMethod) { - super(method); - this.linkage = createLinkage(method); - this.wrapperMethod = wrapperMethod; - } - - private static JNINativeLinkage createLinkage(ResolvedJavaMethod method) { - assert !(method instanceof WrappedJavaMethod); - String className = method.getDeclaringClass().getName(); - String descriptor = method.getSignature().toMethodDescriptor(); - return JNIAccessFeature.singleton().makeLinkage(className, method.getName(), descriptor); - } - - @Override - public int getModifiers() { - // A synchronized method requires some special handling. Instead, if the native method is - // declared synchronized, we add graph nodes to lock and unlock accordingly. - return getOriginal().getModifiers() & ~Modifier.SYNCHRONIZED; - } - - @Override - public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) { - JNIGraphKit kit = new JNIGraphKit(debug, providers, method); - ValueNode callAddress; - if (linkage.isBuiltInFunction()) { - callAddress = kit.unique(new CGlobalDataLoadAddressNode(linkage.getBuiltInAddress())); - SVMImageHeapScanner.instance().rescanField(linkage, linkageBuiltInAddressField); - } else { - callAddress = kit.nativeCallAddress(kit.createObject(linkage)); - } - - JavaType[] params = method.toParameterTypes(); - List args = kit.loadArguments(params); - - int nargs = (method.isStatic() ? 2 : 1) + args.size(); // static must be passed class object - ValueNode[] wrapperArgs = new ValueNode[nargs]; - wrapperArgs[0] = callAddress; - int i = 1; - if (method.isStatic()) { - JavaConstant clazz = providers.getConstantReflection().asJavaClass(method.getDeclaringClass()); - wrapperArgs[i++] = ConstantNode.forConstant(clazz, providers.getMetaAccess(), kit.getGraph()); - } - for (ValueNode arg : args) { - wrapperArgs[i++] = arg; - } - - if (getOriginal().isSynchronized()) { - ValueNode monitorObject = wrapperArgs[1]; - MonitorIdNode monitorId = kit.add(new MonitorIdNode(kit.getFrameState().lockDepth(false))); - MonitorEnterNode monitorEnter = kit.append(new MonitorEnterNode(monitorObject, monitorId)); - kit.getFrameState().pushLock(monitorEnter.object(), monitorEnter.getMonitorId()); - monitorEnter.setStateAfter(kit.getFrameState().create(kit.bci(), monitorEnter)); - } - - ResolvedJavaMethod universeWrapperMethod = wrapperMethod; - UniverseMetaAccess metaAccess = (UniverseMetaAccess) providers.getMetaAccess(); - if (metaAccess.getWrapped() instanceof UniverseMetaAccess) { - universeWrapperMethod = ((UniverseMetaAccess) metaAccess.getWrapped()).getUniverse().lookup(universeWrapperMethod); - } - universeWrapperMethod = metaAccess.getUniverse().lookup(universeWrapperMethod); - ValueNode returnValue = kit.startInvokeWithException(universeWrapperMethod, CallTargetNode.InvokeKind.Static, kit.getFrameState(), kit.bci(), wrapperArgs); - kit.exceptionPart(); - maybeExitMonitor(kit, kit.getFrameState().copy()); - kit.append(new UnwindNode(kit.exceptionObject())); - kit.endInvokeWithException(); - - maybeExitMonitor(kit, kit.getFrameState()); - - JavaType returnType = method.getSignature().getReturnType(null); - JavaKind returnKind = returnType.getJavaKind(); - if (returnKind.isObject()) { - // Just before return to always run the epilogue and never suppress a pending exception - returnValue = kit.checkObjectType(returnValue, (ResolvedJavaType) returnType, false); - } else if (returnKind != JavaKind.Void && JNINativeCallWrapperMethod.returnKindWidensToLong(returnKind)) { - returnValue = kit.maskNumericIntBytes(returnValue, returnKind); - } - kit.createReturn(returnValue, returnKind); - return kit.finalizeGraph(); - } - - private void maybeExitMonitor(JNIGraphKit kit, FrameStateBuilder frameState) { - if (getOriginal().isSynchronized()) { - MonitorIdNode monitorId = frameState.peekMonitorId(); - ValueNode monitorObject = frameState.popLock(); - MonitorExitNode monitorExit = kit.append(new MonitorExitNode(monitorObject, monitorId, null)); - monitorExit.setStateAfter(frameState.create(kit.bci(), monitorExit)); - } - } -} 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 ee5b4b7d8b98..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 @@ -24,91 +24,108 @@ */ package com.oracle.svm.jni.hosted; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import org.graalvm.compiler.debug.DebugContext; +import org.graalvm.compiler.nodes.ConstantNode; import org.graalvm.compiler.nodes.InvokeWithExceptionNode; import org.graalvm.compiler.nodes.StructuredGraph; import org.graalvm.compiler.nodes.ValueNode; -import org.graalvm.compiler.word.WordTypes; +import org.graalvm.compiler.nodes.java.MonitorEnterNode; +import org.graalvm.compiler.nodes.java.MonitorExitNode; +import org.graalvm.compiler.nodes.java.MonitorIdNode; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; import com.oracle.graal.pointsto.meta.HostedProviders; +import com.oracle.svm.core.graal.nodes.CGlobalDataLoadAddressNode; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.core.thread.VMThreads.StatusSupport; -import com.oracle.svm.hosted.code.NonBytecodeStaticMethod; +import com.oracle.svm.hosted.annotation.CustomSubstitutionMethod; import com.oracle.svm.hosted.code.SimpleSignature; -import com.oracle.svm.jni.JNINativeCallWrappers; +import com.oracle.svm.hosted.heap.SVMImageHeapScanner; +import com.oracle.svm.jni.access.JNIAccessFeature; +import com.oracle.svm.jni.access.JNINativeLinkage; import com.oracle.svm.jni.nativeapi.JNIEnvironment; import com.oracle.svm.jni.nativeapi.JNIObjectHandle; +import com.oracle.svm.util.ReflectionUtil; -import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.Constant; +import jdk.vm.ci.meta.JavaConstant; 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 for calling native methods with a specific {@linkplain JNICallSignature form} from - * Java code. The wrapper takes care of transitioning to native code and back to Java, and if - * required, for boxing object arguments in handles, and for unboxing an object return value. + * Generated code for calling a specific native method from Java code. The wrapper takes care of + * transitioning to native code and back to Java, and if required, for boxing object arguments in + * handles and for unboxing an object return value. */ -class JNINativeCallWrapperMethod extends NonBytecodeStaticMethod { - - /** - * 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. - */ - static boolean returnKindWidensToLong(JavaKind returnKind) { - return returnKind.isNumericInteger() || returnKind == JavaKind.Void; +class JNINativeCallWrapperMethod extends CustomSubstitutionMethod { + private final JNINativeLinkage linkage; + private final Field linkageBuiltInAddressField = ReflectionUtil.lookupField(JNINativeLinkage.class, "builtInAddress"); + + JNINativeCallWrapperMethod(ResolvedJavaMethod method) { + super(method); + assert !(method instanceof WrappedJavaMethod); + this.linkage = createLinkage(method); } - static JNICallSignature getSignatureForTarget(ResolvedJavaMethod method, AnalysisUniverse universe, WordTypes wordTypes) { - assert method.isNative() && !method.isConstructor(); - - Signature signature = method.getSignature(); - int count = signature.getParameterCount(false); - JavaKind[] params = new JavaKind[2 + count]; - params[0] = wordTypes.getWordKind(); // native target code address - params[1] = JavaKind.Object; // receiver or class object - for (int i = 0; i < count; i++) { - JavaType paramType = universe.lookupAllowUnresolved(signature.getParameterType(i, null)); - params[2 + i] = wordTypes.asKind(paramType); - } - JavaType returnType = universe.lookupAllowUnresolved(signature.getReturnType(null)); - JavaKind returnKind = wordTypes.asKind(returnType); - if (returnKindWidensToLong(returnKind)) { - returnKind = JavaKind.Long; - } - return new JNICallSignature(params, returnKind, universe.getOriginalMetaAccess()); + private static JNINativeLinkage createLinkage(ResolvedJavaMethod method) { + String className = method.getDeclaringClass().getName(); + String descriptor = method.getSignature().toMethodDescriptor(); + return JNIAccessFeature.singleton().makeLinkage(className, method.getName(), descriptor); } - JNINativeCallWrapperMethod(JNICallSignature signature, MetaAccessProvider originalMetaAccess) { - super("invoke" + signature.getIdentifier(), originalMetaAccess.lookupJavaType(JNINativeCallWrappers.class), - signature, JNINativeCallWrappers.getConstantPool(originalMetaAccess)); + @Override + public int getModifiers() { + // A synchronized method requires some special handling. Instead, if the wrapped method is + // declared synchronized, we add graph nodes to lock and unlock accordingly. + return getOriginal().getModifiers() & ~Modifier.SYNCHRONIZED; } @Override public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) { JNIGraphKit kit = new JNIGraphKit(debug, providers, method); + StructuredGraph graph = kit.getGraph(); + InvokeWithExceptionNode handleFrame = kit.nativeCallPrologue(); + + ValueNode callAddress; + if (linkage.isBuiltInFunction()) { + callAddress = kit.unique(new CGlobalDataLoadAddressNode(linkage.getBuiltInAddress())); + SVMImageHeapScanner.instance().rescanField(linkage, linkageBuiltInAddressField); + } else { + callAddress = kit.nativeCallAddress(kit.createObject(linkage)); + } + ValueNode environment = kit.environment(); JavaType javaReturnType = method.getSignature().getReturnType(null); JavaType[] javaArgumentTypes = method.toParameterTypes(); List javaArguments = kit.loadArguments(javaArgumentTypes); - ValueNode callAddress = javaArguments.get(0); - List jniArguments = new ArrayList<>(1 + javaArguments.size()); - List jniArgumentTypes = new ArrayList<>(1 + javaArguments.size()); + List jniArguments = new ArrayList<>(2 + javaArguments.size()); + List jniArgumentTypes = new ArrayList<>(2 + javaArguments.size()); JavaType environmentType = providers.getMetaAccess().lookupJavaType(JNIEnvironment.class); JavaType objectHandleType = providers.getMetaAccess().lookupJavaType(JNIObjectHandle.class); jniArguments.add(environment); jniArgumentTypes.add(environmentType); - for (int i = 1; i < javaArguments.size(); i++) { + if (method.isStatic()) { + JavaConstant clazz = providers.getConstantReflection().asJavaClass(method.getDeclaringClass()); + ConstantNode clazzNode = ConstantNode.forConstant(clazz, providers.getMetaAccess(), graph); + ValueNode box = kit.boxObjectInLocalHandle(clazzNode); + jniArguments.add(box); + jniArgumentTypes.add(objectHandleType); + } + for (int i = 0; i < javaArguments.size(); i++) { ValueNode arg = javaArguments.get(i); JavaType argType = javaArgumentTypes[i]; - if (argType.getJavaKind().isObject()) { + if (javaArgumentTypes[i].getJavaKind().isObject()) { ValueNode obj = javaArguments.get(i); arg = kit.boxObjectInLocalHandle(obj); argType = objectHandleType; @@ -122,17 +139,44 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, jniReturnType = objectHandleType; } + if (getOriginal().isSynchronized()) { + ValueNode monitorObject; + if (method.isStatic()) { + Constant hubConstant = providers.getConstantReflection().asObjectHub(method.getDeclaringClass()); + DynamicHub hub = (DynamicHub) SubstrateObjectConstant.asObject(hubConstant); + monitorObject = ConstantNode.forConstant(SubstrateObjectConstant.forObject(hub), providers.getMetaAccess(), graph); + } else { + monitorObject = kit.maybeCreateExplicitNullCheck(javaArguments.get(0)); + } + MonitorIdNode monitorId = graph.add(new MonitorIdNode(kit.getFrameState().lockDepth(false))); + MonitorEnterNode monitorEnter = kit.append(new MonitorEnterNode(monitorObject, monitorId)); + kit.getFrameState().pushLock(monitorEnter.object(), monitorEnter.getMonitorId()); + monitorEnter.setStateAfter(kit.getFrameState().create(kit.bci(), monitorEnter)); + } + kit.getFrameState().clearLocals(); Signature jniSignature = new SimpleSignature(jniArgumentTypes, jniReturnType); ValueNode returnValue = kit.createCFunctionCall(callAddress, jniArguments, jniSignature, StatusSupport.STATUS_IN_NATIVE, false); + if (getOriginal().isSynchronized()) { + MonitorIdNode monitorId = kit.getFrameState().peekMonitorId(); + ValueNode monitorObject = kit.getFrameState().popLock(); + MonitorExitNode monitorExit = kit.append(new MonitorExitNode(monitorObject, monitorId, null)); + monitorExit.setStateAfter(kit.getFrameState().create(kit.bci(), monitorExit)); + } + if (javaReturnType.getJavaKind().isObject()) { returnValue = kit.unboxHandle(returnValue); // before destroying handles in epilogue } kit.nativeCallEpilogue(handleFrame); kit.rethrowPendingException(); + if (javaReturnType.getJavaKind().isObject()) { + // Just before return to always run the epilogue and never suppress a pending exception + returnValue = kit.checkObjectType(returnValue, (ResolvedJavaType) javaReturnType, false); + } kit.createReturn(returnValue, javaReturnType.getJavaKind()); + return kit.finalizeGraph(); } } diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperSubstitutionProcessor.java similarity index 59% rename from substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallSubstitutionProcessor.java rename to substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperSubstitutionProcessor.java index cfb06b996eaa..bbde0fb4a8d1 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperSubstitutionProcessor.java @@ -27,53 +27,45 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.graalvm.compiler.word.WordTypes; - import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.jni.JNIJavaCallTrampolines; import com.oracle.svm.jni.access.JNIAccessFeature; +import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; /** - * Substitutes methods declared as {@code native} with {@link JNINativeCallMethod} and - * {@link JNINativeCallWrapperMethod} instances that perform the actual native calls. + * Substitutes methods declared as {@code native} with {@link JNINativeCallWrapperMethod} instances + * that take care of performing the actual native calls. */ -final class JNINativeCallSubstitutionProcessor extends SubstitutionProcessor { - private final AnalysisUniverse universe; - private final WordTypes wordTypes; - +class JNINativeCallWrapperSubstitutionProcessor extends SubstitutionProcessor { + private final MetaAccessProvider originalMetaAccess; private final ResolvedJavaType trampolinesType; - private final Map callers = new ConcurrentHashMap<>(); - private final Map callWrappers = new ConcurrentHashMap<>(); + private final Map callWrappers = new ConcurrentHashMap<>(); - JNINativeCallSubstitutionProcessor(DuringSetupAccessImpl access) { - this.universe = access.getUniverse(); - this.wordTypes = access.getBigBang().getProviders().getWordTypes(); - this.trampolinesType = universe.getOriginalMetaAccess().lookupJavaType(JNIJavaCallTrampolines.class); + JNINativeCallWrapperSubstitutionProcessor(DuringSetupAccessImpl access) { + this.originalMetaAccess = access.getMetaAccess().getWrapped(); + this.trampolinesType = originalMetaAccess.lookupJavaType(JNIJavaCallTrampolines.class); } @Override public ResolvedJavaMethod lookup(ResolvedJavaMethod method) { assert method.isNative() : "Must have been registered as a native substitution processor"; - if (method instanceof JNINativeCallMethod || method instanceof JNINativeCallWrapperMethod) { - return method; // already substituted + if (method instanceof JNINativeCallWrapperMethod) { // already substituted + return method; } if (method.getDeclaringClass().equals(trampolinesType)) { - return JNIAccessFeature.singleton().getOrCreateCallTrampolineMethod(universe.getOriginalMetaAccess(), method.getName()); + return JNIAccessFeature.singleton().getOrCreateCallTrampolineMethod(originalMetaAccess, method.getName()); } - return callers.computeIfAbsent(method, original -> new JNINativeCallMethod(original, - callWrappers.computeIfAbsent(JNINativeCallWrapperMethod.getSignatureForTarget(original, universe, wordTypes), - signature -> new JNINativeCallWrapperMethod(signature, universe.getOriginalMetaAccess())))); + return callWrappers.computeIfAbsent(method, JNINativeCallWrapperMethod::new); } @Override public ResolvedJavaMethod resolve(ResolvedJavaMethod method) { - if (method instanceof JNINativeCallMethod) { - return ((JNINativeCallMethod) method).getOriginal(); + if (method instanceof JNINativeCallWrapperMethod) { + return ((JNINativeCallWrapperMethod) method).getOriginal(); } else if (method instanceof JNICallTrampolineMethod) { return ((JNICallTrampolineMethod) method).getOriginal(); } From 9d279cadd0ce649c4ec6d537dc73a0973e30391d Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 7 Jul 2022 21:33:11 +0200 Subject: [PATCH 10/11] Merge JNICallSignature into SimpleSignature. --- .../svm/hosted/code/SimpleSignature.java | 53 +++++++++ .../svm/jni/access/JNIAccessFeature.java | 12 +- .../svm/jni/hosted/JNICallSignature.java | 110 ------------------ .../JNIJavaCallVariantWrapperMethod.java | 7 +- .../jni/hosted/JNIJavaCallWrapperMethod.java | 17 +-- 5 files changed, 72 insertions(+), 127 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallSignature.java 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.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 cc20bd5d1a54..acb33991e594 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 @@ -65,12 +65,12 @@ 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.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.JNICallSignature; import com.oracle.svm.jni.hosted.JNICallTrampolineMethod; import com.oracle.svm.jni.hosted.JNIFieldAccessorMethod; import com.oracle.svm.jni.hosted.JNIJavaCallVariantWrapperMethod; @@ -109,9 +109,9 @@ static final class JNIJavaCallVariantWrapperGroup { private boolean sealed = false; 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 final Map javaCallWrapperMethods = new ConcurrentHashMap<>(); + private final Map callVariantWrappers = new ConcurrentHashMap<>(); + private final Map nonvirtualCallVariantWrappers = new ConcurrentHashMap<>(); private int loadedConfigurations; @@ -326,7 +326,7 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) { newObjectMethod = aFactoryMethod.getWrapped(); } - JNICallSignature compatibleSignature = JNIJavaCallWrapperMethod.getGeneralizedSignatureForTarget(targetMethod, originalMetaAccess); + 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); @@ -342,7 +342,7 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) { }); } - private JNIJavaCallVariantWrapperGroup createJavaCallVariantWrappers(DuringAnalysisAccessImpl access, JNICallSignature wrapperSignature, boolean nonVirtual) { + private JNIJavaCallVariantWrapperGroup createJavaCallVariantWrappers(DuringAnalysisAccessImpl access, SimpleSignature wrapperSignature, boolean nonVirtual) { var map = nonVirtual ? nonvirtualCallVariantWrappers : callVariantWrappers; return map.computeIfAbsent(wrapperSignature, signature -> { MetaAccessProvider originalMetaAccess = access.getUniverse().getOriginalMetaAccess(); 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 deleted file mode 100644 index 65b6b6ec0dbe..000000000000 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallSignature.java +++ /dev/null @@ -1,110 +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.hosted; - -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; -import jdk.vm.ci.meta.ResolvedJavaType; -import jdk.vm.ci.meta.Signature; - -public class JNICallSignature implements Signature { - - private final JavaType[] paramTypes; - private final JavaType returnType; - - JNICallSignature(JavaType[] paramTypes, JavaType returnType) { - assert Arrays.stream(paramTypes).noneMatch(WrappedJavaType.class::isInstance) && !(returnType instanceof WrappedJavaType); - this.paramTypes = paramTypes; - this.returnType = returnType; - } - - 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); - } - this.returnType = resolveType(returnKind, originalMetaAccess); - } - - private static ResolvedJavaType resolveType(JavaKind kind, MetaAccessProvider metaAccess) { - return metaAccess.lookupJavaType(kind.isObject() ? Object.class : 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; - } - } - 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 paramTypes[index]; - } - - @Override - public JavaType getReturnType(ResolvedJavaType accessingClass) { - return returnType; - } - - @Override - public boolean equals(Object obj) { - if (this != obj && obj instanceof JNICallSignature) { - var other = (JNICallSignature) obj; - return Arrays.equals(paramTypes, other.paramTypes) && Objects.equals(returnType, other.returnType); - } - return (this == obj); - } - - @Override - public int hashCode() { - return Arrays.hashCode(paramTypes) * 31 + Objects.hashCode(returnType); - } - - @Override - public String toString() { - return getIdentifier(); - } -} 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 db1d7029896c..78a5e87d2496 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 @@ -62,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.code.SimpleSignature; import com.oracle.svm.hosted.meta.HostedMetaAccess; import com.oracle.svm.jni.JNIJavaCallVariantWrappers; @@ -82,7 +83,7 @@ public enum CallVariant { private final CallVariant callVariant; private final boolean nonVirtual; - public JNIJavaCallVariantWrapperMethod(JNICallSignature callWrapperSignature, CallVariant callVariant, boolean nonVirtual, MetaAccessProvider originalMetaAccess, WordTypes wordTypes) { + 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), @@ -92,7 +93,7 @@ public JNIJavaCallVariantWrapperMethod(JNICallSignature callWrapperSignature, Ca this.nonVirtual = nonVirtual; } - private static String createName(JNICallSignature targetSignature, CallVariant callVariant, boolean nonVirtual) { + private static String createName(SimpleSignature targetSignature, CallVariant callVariant, boolean nonVirtual) { return "invoke" + targetSignature.getIdentifier() + "_" + callVariant.name() + (nonVirtual ? "_Nonvirtual" : ""); } @@ -129,7 +130,7 @@ private static Signature createSignature(Signature callWrapperSignature, CallVar if (returnType.isObject()) { returnType = wordKind; // handle } - return new JNICallSignature(args.toArray(JavaKind[]::new), returnType, originalMetaAccess); + return SimpleSignature.fromKinds(args.toArray(JavaKind[]::new), returnType, originalMetaAccess); } @Override 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 988f111a0279..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 @@ -60,6 +60,7 @@ 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.code.SimpleSignature; import com.oracle.svm.hosted.meta.HostedMetaAccess; import com.oracle.svm.jni.JNIJavaCallWrappers; import com.oracle.svm.jni.access.JNIAccessibleMethod; @@ -89,7 +90,7 @@ public class JNIJavaCallWrapperMethod extends NonBytecodeStaticMethod { private static final Constructor INSTANTIATION_EXCEPTION_CONSTRUCTOR = ReflectionUtil.lookupConstructor(InstantiationException.class); public static class Factory { - public JNIJavaCallWrapperMethod create(JNICallSignature targetSignature, MetaAccessProvider originalMetaAccess, WordTypes wordTypes) { + public JNIJavaCallWrapperMethod create(SimpleSignature targetSignature, MetaAccessProvider originalMetaAccess, WordTypes wordTypes) { return new JNIJavaCallWrapperMethod(targetSignature, originalMetaAccess, wordTypes); } @@ -99,7 +100,7 @@ public boolean canInvokeConstructorOnObject(ResolvedJavaMethod constructor, Meta } } - public static JNICallSignature getGeneralizedSignatureForTarget(ResolvedJavaMethod targetMethod, MetaAccessProvider originalMetaAccess) { + 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. @@ -114,18 +115,18 @@ public static JNICallSignature getGeneralizedSignatureForTarget(ResolvedJavaMeth // 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); + return new SimpleSignature(paramTypes, returnType); } private final Signature targetSignature; - protected JNIJavaCallWrapperMethod(JNICallSignature targetSignature, MetaAccessProvider metaAccess, WordTypes wordTypes) { + 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 JNICallSignature createSignature(Signature targetSignature, MetaAccessProvider originalMetaAccess, WordTypes wordTypes) { + 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]; @@ -151,12 +152,12 @@ private static JNICallSignature createSignature(Signature targetSignature, MetaA if (returnKind.isObject()) { returnKind = wordKind; // handle } - return new JNICallSignature(args, returnKind, originalMetaAccess); + return SimpleSignature.fromKinds(args, returnKind, originalMetaAccess); } @Override - public JNICallSignature getSignature() { - return (JNICallSignature) super.getSignature(); + public SimpleSignature getSignature() { + return (SimpleSignature) super.getSignature(); } @Override From 0e0a4a2dd7e8d820696f6ade1fb9494bbd97584d Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 7 Jul 2022 21:07:52 +0200 Subject: [PATCH 11/11] Update code documentation. --- .../svm/jni/access/JNIAccessFeature.java | 1 + .../svm/jni/hosted/JNICallWrapperFeature.java | 35 +++++++++++-------- .../JNIJavaCallVariantWrapperMethod.java | 5 +++ 3 files changed, 26 insertions(+), 15 deletions(-) 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 acb33991e594..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 @@ -93,6 +93,7 @@ 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); 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/JNIJavaCallVariantWrapperMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNIJavaCallVariantWrapperMethod.java index 78a5e87d2496..42cf94b26ae2 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 @@ -72,6 +72,11 @@ 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,