From 251273f530254ad6714309bd654d8c80ae41b98d Mon Sep 17 00:00:00 2001 From: Christian Wimmer Date: Fri, 10 Sep 2021 09:05:45 -0700 Subject: [PATCH] Fix and improve Graal IR for reflection --- ...bstrateReflectionAccessorFactoryJDK11.java | 8 +- ...ubstrateReflectionAccessorFactoryJDK8.java | 8 +- .../graal/replacements/SubstrateGraphKit.java | 10 +- .../svm/core/invoke/MethodHandleUtils.java | 14 - .../reflect/ReflectionAccessorHolder.java | 67 +++- .../reflect/SubstrateConstructorAccessor.java | 1 + .../core/reflect/SubstrateMethodAccessor.java | 19 +- .../SubstrateReflectionAccessorFactory.java | 2 +- .../svm/core/util/ExceptionHelpers.java | 60 ---- .../svm/hosted/phases/HostedGraphKit.java | 8 - .../PolymorphicSignatureWrapperMethod.java | 2 +- .../com/oracle/svm/jni/JNIObjectHandles.java | 8 +- .../svm/reflect/hosted/ReflectionFeature.java | 36 +- .../reflect/hosted/ReflectionGraphKit.java | 323 ++++++++++++++++++ .../svm/reflect/hosted/ReflectionMethod.java | 145 -------- .../hosted/ReflectiveInvokeMethod.java | 207 +++++++---- .../hosted/ReflectiveNewInstanceMethod.java | 52 ++- 17 files changed, 598 insertions(+), 372 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ExceptionHelpers.java create mode 100644 substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionGraphKit.java delete mode 100644 substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionMethod.java diff --git a/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/reflect/SubstrateReflectionAccessorFactoryJDK11.java b/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/reflect/SubstrateReflectionAccessorFactoryJDK11.java index 6bad5e5a6991..5a68d88eae0e 100644 --- a/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/reflect/SubstrateReflectionAccessorFactoryJDK11.java +++ b/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/reflect/SubstrateReflectionAccessorFactoryJDK11.java @@ -39,8 +39,8 @@ final class SubstrateReflectionAccessorFactoryJDK11 implements SubstrateReflectionAccessorFactory { @Override - public SubstrateMethodAccessor createMethodAccessor(Executable member, CFunctionPointer invokeFunctionPointer, CFunctionPointer invokeSpecialFunctionPointer) { - return new SubstrateMethodAccessorJDK11(member, invokeFunctionPointer, invokeSpecialFunctionPointer); + public SubstrateMethodAccessor createMethodAccessor(Executable member, CFunctionPointer invokeFunctionPointer) { + return new SubstrateMethodAccessorJDK11(member, invokeFunctionPointer); } @Override @@ -58,8 +58,8 @@ public void afterRegistration(AfterRegistrationAccess access) { } final class SubstrateMethodAccessorJDK11 extends SubstrateMethodAccessor implements jdk.internal.reflect.MethodAccessor { - SubstrateMethodAccessorJDK11(Executable member, CFunctionPointer invokeFunctionPointer, CFunctionPointer invokeSpecialFunctionPointer) { - super(member, invokeFunctionPointer, invokeSpecialFunctionPointer); + SubstrateMethodAccessorJDK11(Executable member, CFunctionPointer invokeFunctionPointer) { + super(member, invokeFunctionPointer); } } diff --git a/substratevm/src/com.oracle.svm.core.jdk8/src/com/oracle/svm/core/jdk8/reflect/SubstrateReflectionAccessorFactoryJDK8.java b/substratevm/src/com.oracle.svm.core.jdk8/src/com/oracle/svm/core/jdk8/reflect/SubstrateReflectionAccessorFactoryJDK8.java index 35a9909ad4e3..3b65a375c8aa 100644 --- a/substratevm/src/com.oracle.svm.core.jdk8/src/com/oracle/svm/core/jdk8/reflect/SubstrateReflectionAccessorFactoryJDK8.java +++ b/substratevm/src/com.oracle.svm.core.jdk8/src/com/oracle/svm/core/jdk8/reflect/SubstrateReflectionAccessorFactoryJDK8.java @@ -39,8 +39,8 @@ final class SubstrateReflectionAccessorFactoryJDK8 implements SubstrateReflectionAccessorFactory { @Override - public SubstrateMethodAccessor createMethodAccessor(Executable member, CFunctionPointer invokeFunctionPointer, CFunctionPointer invokeSpecialFunctionPointer) { - return new SubstrateMethodAccessorJDK8(member, invokeFunctionPointer, invokeSpecialFunctionPointer); + public SubstrateMethodAccessor createMethodAccessor(Executable member, CFunctionPointer invokeFunctionPointer) { + return new SubstrateMethodAccessorJDK8(member, invokeFunctionPointer); } @Override @@ -58,8 +58,8 @@ public void afterRegistration(AfterRegistrationAccess access) { } final class SubstrateMethodAccessorJDK8 extends SubstrateMethodAccessor implements sun.reflect.MethodAccessor { - SubstrateMethodAccessorJDK8(Executable member, CFunctionPointer invokeFunctionPointer, CFunctionPointer invokeSpecialFunctionPointer) { - super(member, invokeFunctionPointer, invokeSpecialFunctionPointer); + SubstrateMethodAccessorJDK8(Executable member, CFunctionPointer invokeFunctionPointer) { + super(member, invokeFunctionPointer); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/replacements/SubstrateGraphKit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/replacements/SubstrateGraphKit.java index 202d4d431b66..a0bb23df4cba 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/replacements/SubstrateGraphKit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/replacements/SubstrateGraphKit.java @@ -55,6 +55,7 @@ import org.graalvm.compiler.nodes.calc.FloatingNode; import org.graalvm.compiler.nodes.calc.NarrowNode; import org.graalvm.compiler.nodes.extended.BoxNode; +import org.graalvm.compiler.nodes.extended.GuardingNode; import org.graalvm.compiler.nodes.extended.StateSplitProxyNode; import org.graalvm.compiler.nodes.extended.UnboxNode; import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; @@ -84,7 +85,6 @@ import jdk.vm.ci.meta.Constant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaType; -import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @@ -148,8 +148,8 @@ public LoadFieldNode createLoadField(ValueNode object, ResolvedJavaField field) return append(LoadFieldNode.create(null, object, field)); } - public ValueNode createLoadIndexed(ValueNode array, int index, JavaKind kind) { - ValueNode loadIndexed = LoadIndexedNode.create(null, array, ConstantNode.forInt(index, getGraph()), null, kind, getMetaAccess(), getConstantReflection()); + public ValueNode createLoadIndexed(ValueNode array, int index, JavaKind kind, GuardingNode boundsCheck) { + ValueNode loadIndexed = LoadIndexedNode.create(null, array, ConstantNode.forInt(index, getGraph()), boundsCheck, kind, getMetaAccess(), getConstantReflection()); if (loadIndexed instanceof FixedNode) { return append((FixedNode) loadIndexed); } @@ -160,8 +160,8 @@ public ValueNode createStoreIndexed(ValueNode array, int index, JavaKind kind, V return append(new StoreIndexedNode(array, ConstantNode.forInt(index, getGraph()), null, null, kind, value)); } - public ValueNode createUnboxing(ValueNode boxed, JavaKind targetKind, MetaAccessProvider metaAccess) { - return append(new UnboxNode(boxed, targetKind, metaAccess)); + public ValueNode createUnboxing(ValueNode boxed, JavaKind targetKind) { + return append(new UnboxNode(boxed, targetKind, getMetaAccess())); } public ValueNode createInvokeWithExceptionAndUnwind(Class declaringClass, String name, InvokeKind invokeKind, ValueNode... args) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/MethodHandleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/MethodHandleUtils.java index 655fd827e97e..5db19d468031 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/MethodHandleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/MethodHandleUtils.java @@ -32,8 +32,6 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.annotate.AlwaysInline; -import jdk.vm.ci.meta.MetaAccessProvider; -import jdk.vm.ci.meta.ResolvedJavaMethod; // Checkstyle: stop import sun.invoke.util.Wrapper; // Checkstyle: resume @@ -133,18 +131,6 @@ public static short shortUnbox(Object retVal, Class returnType) { } } - public static ResolvedJavaMethod getThrowUnsupportedOperationException(MetaAccessProvider metaAccess) { - try { - return metaAccess.lookupJavaMethod(MethodHandleUtils.class.getMethod("throwUnsupportedOperationException")); - } catch (NoSuchMethodException e) { - throw shouldNotReachHere(); - } - } - - public static void throwUnsupportedOperationException() { - throw new UnsupportedOperationException("MethodHandle.invoke() and MethodHandle.invokeExact() cannot be invoked through reflection"); - } - public static class MethodHandlesSupported implements BooleanSupplier { @Override public boolean getAsBoolean() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionAccessorHolder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionAccessorHolder.java index 2e8098cef638..2b7044246374 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionAccessorHolder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/ReflectionAccessorHolder.java @@ -24,7 +24,14 @@ */ package com.oracle.svm.core.reflect; +// Checkstyle: allow reflection + +import java.lang.reflect.InvocationTargetException; + +import com.oracle.svm.core.annotate.NeverInline; import com.oracle.svm.core.jdk.InternalVMMethod; +import com.oracle.svm.core.reflect.SubstrateConstructorAccessor.ConstructorNewInstanceFunctionPointer; +import com.oracle.svm.core.reflect.SubstrateMethodAccessor.MethodInvokeFunctionPointer; import com.oracle.svm.core.util.VMError; /** @@ -37,28 +44,76 @@ public final class ReflectionAccessorHolder { /** - * Signature prototype for invoking a method via a {@link SubstrateMethodAccessor}. + * Signature prototype for invoking a method via a {@link SubstrateMethodAccessor}. Must match + * the signature of {@link MethodInvokeFunctionPointer#invoke} */ - private static Object invokePrototype(Object obj, Object[] args) { + static Object invokePrototype(boolean invokeSpecial, Object obj, Object[] args) { throw VMError.shouldNotReachHere("Only used as a prototype for generated methods"); } /** * Signature prototype for allocating a new instance via a {@link SubstrateConstructorAccessor}. + * Must match * the signature of {@link ConstructorNewInstanceFunctionPointer#invoke} */ - private static Object newInstancePrototype(Object[] args) { + static Object newInstancePrototype(Object[] args) { throw VMError.shouldNotReachHere("Only used as a prototype for generated methods"); } /* - * Methods for throwing exceptions when a method or constructor is used in an illegal way. + * Methods for throwing exceptions when a method or constructor is used in an illegal way. These + * methods are invoked via function pointers, so must have the same signature as the prototypes + * above. */ - private static Object invokeSpecialError(Object obj, Object[] args) { - throw new IllegalArgumentException("Static or abstract method cannot be invoked using invokeSpecial"); + private static void methodHandleInvokeError(boolean invokeSpecial, Object obj, Object[] args) throws InvocationTargetException { + /* The nested exceptions are required by the specification. */ + throw new InvocationTargetException(new UnsupportedOperationException("MethodHandle.invoke() and MethodHandle.invokeExact() cannot be invoked through reflection")); } private static Object newInstanceError(Object[] args) throws InstantiationException { throw new InstantiationException("Only non-abstract instance classes can be instantiated using reflection"); } + + /* + * Methods for throwing exceptions from within the generated Graal IR. The signature depends on + * the call site, i.e., it does not need to be the prototype signature. + */ + + @NeverInline("Exception slow path") + private static InvocationTargetException throwInvocationTargetException(Throwable target) throws InvocationTargetException { + throw new InvocationTargetException(target); + } + + @NeverInline("Exception slow path") + private static void throwIllegalArgumentExceptionForMethod(Object member, Object obj, Object[] args) { + throwIllegalArgumentException(member, false, obj, args); + } + + @NeverInline("Exception slow path") + private static void throwIllegalArgumentExceptionForConstructor(Object member, Object[] args) { + throwIllegalArgumentException(member, true, null, args); + } + + /** + * We do not know which check in the generated metod caused the exception, so we cannot print + * detailed information about that. But printing the signature of the method and all the types + * of the actual arguments should make it obvious what the problem is. + */ + private static void throwIllegalArgumentException(Object member, boolean constructor, Object obj, Object[] args) { + String sep = System.lineSeparator(); + StringBuilder msg = new StringBuilder(); + msg.append("Illegal arguments for invoking ").append(member); + if (!constructor) { + msg.append(sep).append(" obj: ").append(obj == null ? "null" : obj.getClass().getTypeName()); + } + if (args == null) { + msg.append(sep).append(" args: null"); + } else { + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + msg.append(sep).append(" args[").append(i).append("]: ").append(arg == null ? "null" : arg.getClass().getTypeName()); + } + } + throw new IllegalArgumentException(msg.toString()); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateConstructorAccessor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateConstructorAccessor.java index 1a0177d3f3f6..6902ece0f28e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateConstructorAccessor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateConstructorAccessor.java @@ -38,6 +38,7 @@ public abstract class SubstrateConstructorAccessor { interface ConstructorNewInstanceFunctionPointer extends CFunctionPointer { + /** Must match the signature of {@link ReflectionAccessorHolder#newInstancePrototype}. */ @InvokeJavaFunctionPointer Object invoke(Object[] args); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java index 9628468531bf..18609f547956 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java @@ -38,18 +38,17 @@ public abstract class SubstrateMethodAccessor { interface MethodInvokeFunctionPointer extends CFunctionPointer { + /** Must match the signature of {@link ReflectionAccessorHolder#invokePrototype}. */ @InvokeJavaFunctionPointer - Object invoke(Object obj, Object[] args); + Object invoke(boolean invokeSpecial, Object obj, Object[] args); } private final Executable member; private final CFunctionPointer invokeFunctionPointer; - private final CFunctionPointer invokeSpecialFunctionPointer; - protected SubstrateMethodAccessor(Executable member, CFunctionPointer invokeFunctionPointer, CFunctionPointer invokeSpecialFunctionPointer) { + protected SubstrateMethodAccessor(Executable member, CFunctionPointer invokeFunctionPointer) { this.member = member; this.invokeFunctionPointer = invokeFunctionPointer; - this.invokeSpecialFunctionPointer = invokeSpecialFunctionPointer; } public Object invoke(Object obj, Object[] args) { @@ -57,7 +56,7 @@ public Object invoke(Object obj, Object[] args) { if (functionPointer.isNull()) { throw invokeError(); } - return functionPointer.invoke(obj, args); + return functionPointer.invoke(false, obj, args); } private RuntimeException invokeError() { @@ -65,14 +64,10 @@ private RuntimeException invokeError() { } public Object invokeSpecial(Object obj, Object[] args) { - MethodInvokeFunctionPointer functionPointer = (MethodInvokeFunctionPointer) this.invokeSpecialFunctionPointer; + MethodInvokeFunctionPointer functionPointer = (MethodInvokeFunctionPointer) this.invokeFunctionPointer; if (functionPointer.isNull()) { - throw invokeSpecialError(); + throw invokeError(); } - return functionPointer.invoke(obj, args); - } - - private RuntimeException invokeSpecialError() { - throw VMError.shouldNotReachHere("No SubstrateMethodAccessor.invokeSpecialFunctionPointer for " + member); + return functionPointer.invoke(true, obj, args); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateReflectionAccessorFactory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateReflectionAccessorFactory.java index e37b81c5dde1..936ba9dcc9c0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateReflectionAccessorFactory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateReflectionAccessorFactory.java @@ -31,7 +31,7 @@ import org.graalvm.nativeimage.c.function.CFunctionPointer; public interface SubstrateReflectionAccessorFactory { - SubstrateMethodAccessor createMethodAccessor(Executable member, CFunctionPointer invokeFunctionPointer, CFunctionPointer invokeSpecialFunctionPointer); + SubstrateMethodAccessor createMethodAccessor(Executable member, CFunctionPointer invokeFunctionPointer); SubstrateConstructorAccessor createConstructorAccessor(Executable member, CFunctionPointer newInstanceFunctionPointer); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ExceptionHelpers.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ExceptionHelpers.java deleted file mode 100644 index 92dd19ac769d..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ExceptionHelpers.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2017, 2017, 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.core.util; - -// Checkstyle: allow reflection - -import java.lang.reflect.InvocationTargetException; - -import com.oracle.svm.core.annotate.NeverInline; -import com.oracle.svm.core.annotate.StubCallingConvention; -import com.oracle.svm.core.jdk.InternalVMMethod; - -/** - * Helper methods to throw exceptions from manually generated Graal graphs. We do not want these - * helpers to show up in exception stack traces, therefore the class is annotated with - * {@link InternalVMMethod}. - */ -@InternalVMMethod -public class ExceptionHelpers { - - @StubCallingConvention - @NeverInline("Exception slow path") - private static InvocationTargetException throwInvocationTargetException(Throwable target) throws InvocationTargetException { - throw new InvocationTargetException(target); - } - - @StubCallingConvention - @NeverInline("Exception slow path") - public static IllegalArgumentException throwIllegalArgumentException(String message) { - throw new IllegalArgumentException(message, null); - } - - @StubCallingConvention - @NeverInline("Exception slow path") - private static IllegalArgumentException throwFailedCast(Class expected, Object actual) { - throw new IllegalArgumentException("cannot cast " + actual.getClass().getName() + " to " + expected.getName(), null); - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java index 512d21d91466..7d0a227d21b6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java @@ -57,10 +57,8 @@ import com.oracle.svm.core.c.BoxedRelocatedPointer; import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; import com.oracle.svm.core.graal.code.SubstrateCompilationIdentifier; -import com.oracle.svm.core.graal.nodes.LoweredDeadEndNode; import com.oracle.svm.core.graal.replacements.SubstrateGraphKit; import com.oracle.svm.core.nodes.SubstrateMethodCallTargetNode; -import com.oracle.svm.core.util.ExceptionHelpers; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.meta.HostedMethod; @@ -115,12 +113,6 @@ public T appendWithUnwind(T withExceptionNode) { return appendWithUnwind(withExceptionNode, bci()); } - public void throwInvocationTargetException(ValueNode exception) { - ResolvedJavaMethod throwInvocationTargetException = findMethod(ExceptionHelpers.class, "throwInvocationTargetException", true); - createJavaCallWithExceptionAndUnwind(InvokeKind.Static, throwInvocationTargetException, exception); - append(new LoweredDeadEndNode()); - } - public LoadFieldNode createLoadFieldNode(ConstantNode receiver, Class clazz, String fieldName) { try { ResolvedJavaType type = getMetaAccess().lookupJavaType(clazz); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/PolymorphicSignatureWrapperMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/PolymorphicSignatureWrapperMethod.java index b2513f13c3e7..2e8e64012b35 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/PolymorphicSignatureWrapperMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/PolymorphicSignatureWrapperMethod.java @@ -173,7 +173,7 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, retVal = kit.createInvokeWithExceptionAndUnwind(unboxMethod, CallTargetNode.InvokeKind.Static, kit.getFrameState(), kit.bci(), retVal, methodHandleOrMemberName); break; default: - retVal = kit.createUnboxing(invoke, returnKind, metaAccess); + retVal = kit.createUnboxing(invoke, returnKind); } } kit.createReturn(retVal, returnKind); 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 271ebd3a0094..42479d409c0a 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 @@ -43,7 +43,6 @@ import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalObject; -import com.oracle.svm.core.util.ExceptionHelpers; import com.oracle.svm.jni.nativeapi.JNIObjectHandle; import com.oracle.svm.jni.nativeapi.JNIObjectRefType; @@ -141,7 +140,12 @@ public static T getObject(JNIObjectHandle handle) { return JNIGlobalHandles.getObject(handle); } - throw ExceptionHelpers.throwIllegalArgumentException("Invalid object handle"); + throw throwIllegalArgumentException(); + } + + @NeverInline("Exception slow path") + private static IllegalArgumentException throwIllegalArgumentException() { + throw new IllegalArgumentException("Invalid object handle"); } public static JNIObjectRefType getHandleType(JNIObjectHandle handle) { diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java index e55a1e1b6a03..8c27c5ccee5d 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java @@ -26,6 +26,7 @@ // Checkstyle: allow reflection +import java.lang.invoke.MethodHandle; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; @@ -83,9 +84,9 @@ public class ReflectionFeature implements GraalFeature { final Map accessors = new ConcurrentHashMap<>(); - private static final Method invokePrototype = ReflectionUtil.lookupMethod(ReflectionAccessorHolder.class, "invokePrototype", Object.class, Object[].class); + private static final Method invokePrototype = ReflectionUtil.lookupMethod(ReflectionAccessorHolder.class, "invokePrototype", boolean.class, Object.class, Object[].class); private static final Method newInstancePrototype = ReflectionUtil.lookupMethod(ReflectionAccessorHolder.class, "newInstancePrototype", Object[].class); - private static final Method invokeSpecialErrorMethod = ReflectionUtil.lookupMethod(ReflectionAccessorHolder.class, "invokeSpecialError", Object.class, Object[].class); + private static final Method methodHandleInvokeErrorMethod = ReflectionUtil.lookupMethod(ReflectionAccessorHolder.class, "methodHandleInvokeError", boolean.class, Object.class, Object[].class); private static final Method newInstanceErrorMethod = ReflectionUtil.lookupMethod(ReflectionAccessorHolder.class, "newInstanceError", Object[].class); FeatureImpl.BeforeAnalysisAccessImpl analysisAccess; @@ -119,28 +120,15 @@ Object getOrCreateAccessor(Executable member) { private Object createAccessor(Executable member) { String name = SubstrateUtil.uniqueShortName(member); if (member instanceof Method) { - ResolvedJavaMethod prototype = analysisAccess.getMetaAccess().lookupJavaMethod(invokePrototype).getWrapped(); - ResolvedJavaMethod invokeMethod = createReflectiveInvokeMethod(name, prototype, member, false); - - ResolvedJavaMethod invokeSpecialMethod; - AnalysisMethod targetMethod = analysisAccess.getMetaAccess().lookupJavaMethod(member); - if (targetMethod.isStatic() || targetMethod.isAbstract()) { - /* - * Static / abstract methods cannot be invoked using an invokeSpecial. Such direct - * calls (instead of virtual calls) are not available via the Java reflection API, - * but used by our method handle implementation. - */ - invokeSpecialMethod = analysisAccess.getMetaAccess().lookupJavaMethod(invokeSpecialErrorMethod); - } else if (targetMethod.canBeStaticallyBound()) { - /* - * Methods that can be statically bound are always de-virtualized to a special - * invoke, so no separate invocation stub is necessary. - */ - invokeSpecialMethod = invokeMethod; + ResolvedJavaMethod invokeMethod; + if (member.getDeclaringClass() == MethodHandle.class && (member.getName().equals("invoke") || member.getName().equals("invokeExact"))) { + /* Method handles must not be invoked via reflection. */ + invokeMethod = analysisAccess.getMetaAccess().lookupJavaMethod(methodHandleInvokeErrorMethod); } else { - invokeSpecialMethod = createReflectiveInvokeMethod("invokeSpecial_" + name, prototype, member, true); + ResolvedJavaMethod prototype = analysisAccess.getMetaAccess().lookupJavaMethod(invokePrototype).getWrapped(); + invokeMethod = createReflectiveInvokeMethod(name, prototype, (Method) member); } - return ImageSingletons.lookup(SubstrateReflectionAccessorFactory.class).createMethodAccessor(member, register(invokeMethod), register(invokeSpecialMethod)); + return ImageSingletons.lookup(SubstrateReflectionAccessorFactory.class).createMethodAccessor(member, register(invokeMethod)); } else { ResolvedJavaMethod newInstanceMethod; @@ -167,8 +155,8 @@ private CFunctionPointer register(ResolvedJavaMethod method) { return MethodPointer.factory(aMethod); } - protected ResolvedJavaMethod createReflectiveInvokeMethod(String name, ResolvedJavaMethod prototype, Executable member, boolean specialInvoke) { - return new ReflectiveInvokeMethod(name, prototype, (Method) member, specialInvoke); + protected ResolvedJavaMethod createReflectiveInvokeMethod(String name, ResolvedJavaMethod prototype, Method method) { + return new ReflectiveInvokeMethod(name, prototype, method); } protected ResolvedJavaMethod createReflectiveNewInstanceMethod(String name, ResolvedJavaMethod prototype, Constructor constructor) { diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionGraphKit.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionGraphKit.java new file mode 100644 index 000000000000..86afda24ca46 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionGraphKit.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2021, 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.reflect.hosted; + +// Checkstyle: allow reflection + +import java.lang.reflect.Executable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.graalvm.compiler.core.common.calc.FloatConvert; +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.AbstractBeginNode; +import org.graalvm.compiler.nodes.AbstractMergeNode; +import org.graalvm.compiler.nodes.CallTargetNode; +import org.graalvm.compiler.nodes.ConstantNode; +import org.graalvm.compiler.nodes.EndNode; +import org.graalvm.compiler.nodes.FixedWithNextNode; +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.ValueNode; +import org.graalvm.compiler.nodes.ValuePhiNode; +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.SignExtendNode; +import org.graalvm.compiler.nodes.extended.BranchProbabilityNode; +import org.graalvm.compiler.nodes.extended.GuardingNode; +import org.graalvm.compiler.nodes.java.ArrayLengthNode; +import org.graalvm.compiler.nodes.java.ExceptionObjectNode; +import org.graalvm.compiler.nodes.java.InstanceOfNode; +import org.graalvm.compiler.nodes.type.StampTool; + +import com.oracle.graal.pointsto.meta.HostedProviders; +import com.oracle.svm.core.graal.nodes.LoweredDeadEndNode; +import com.oracle.svm.core.meta.SubstrateObjectConstant; +import com.oracle.svm.core.reflect.ReflectionAccessorHolder; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.phases.HostedGraphKit; + +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +public class ReflectionGraphKit extends HostedGraphKit { + + static final Map> PRIMITIVE_UNBOXINGS; + static { + PRIMITIVE_UNBOXINGS = new HashMap<>(); + /*- Widening conversions (from JVM spec): + * byte to short, int, long, float, or double + * short to int, long, float, or double + * char to int, long, float, or double + * int to long, float, or double + * long to float or double + * float to double + * + * The list elements are checked at run time in that order, so the order affects how many + * type checks are done. The exact fit is always first, and then we just guess what is + * likely. + */ + PRIMITIVE_UNBOXINGS.put(JavaKind.Boolean, Arrays.asList(JavaKind.Boolean)); + PRIMITIVE_UNBOXINGS.put(JavaKind.Byte, Arrays.asList(JavaKind.Byte)); + PRIMITIVE_UNBOXINGS.put(JavaKind.Short, Arrays.asList(JavaKind.Short, JavaKind.Byte)); + PRIMITIVE_UNBOXINGS.put(JavaKind.Char, Arrays.asList(JavaKind.Char)); + PRIMITIVE_UNBOXINGS.put(JavaKind.Int, Arrays.asList(JavaKind.Int, JavaKind.Byte, JavaKind.Short, JavaKind.Char)); + PRIMITIVE_UNBOXINGS.put(JavaKind.Long, Arrays.asList(JavaKind.Long, JavaKind.Int, JavaKind.Byte, JavaKind.Short, JavaKind.Char)); + PRIMITIVE_UNBOXINGS.put(JavaKind.Float, Arrays.asList(JavaKind.Float, JavaKind.Int, JavaKind.Long, JavaKind.Byte, JavaKind.Short, JavaKind.Char)); + PRIMITIVE_UNBOXINGS.put(JavaKind.Double, Arrays.asList(JavaKind.Double, JavaKind.Float, JavaKind.Int, JavaKind.Long, JavaKind.Byte, JavaKind.Short, JavaKind.Char)); + } + + private final List illegalArgumentExceptionPaths = new ArrayList<>(); + private final List invocationTargetExceptionPaths = new ArrayList<>(); + + public ReflectionGraphKit(DebugContext debug, HostedProviders providers, ResolvedJavaMethod method) { + super(debug, providers, method); + } + + @Override + public AbstractMergeNode endIf() { + AbstractMergeNode merge = super.endIf(); + if (merge != null) { + merge.setStateAfter(getFrameState().create(bci(), merge)); + } + return merge; + } + + public void branchToIllegalArgumentException() { + illegalArgumentExceptionPaths.add(lastFixedNode); + lastFixedNode = null; + } + + public void branchToInvocationTargetException(ExceptionObjectNode exceptionObjectNode) { + assert exceptionObjectNode == lastFixedNode; + invocationTargetExceptionPaths.add(exceptionObjectNode); + lastFixedNode = null; + } + + /** + * To reduce machine code size, we want only one call site for each exception class that can be + * thrown. We also do not want arguments that require phi functions. So we cannot have an error + * message that, e.g., prints which exact cast failed. But we have the member that the stub is + * for, and the provided argument arrays, which allows a good enough error message. + */ + public void emitIllegalArgumentException(Executable member, ValueNode obj, ValueNode args) { + continueWithMerge(illegalArgumentExceptionPaths); + ValueNode memberNode = createConstant(SubstrateObjectConstant.forObject(member), JavaKind.Object); + ResolvedJavaMethod targetMethod; + ValueNode[] arguments; + if (obj == null) { + targetMethod = findMethod(ReflectionAccessorHolder.class, "throwIllegalArgumentExceptionForConstructor", true); + arguments = new ValueNode[]{memberNode, args}; + } else { + targetMethod = findMethod(ReflectionAccessorHolder.class, "throwIllegalArgumentExceptionForMethod", true); + arguments = new ValueNode[]{memberNode, obj, args}; + } + createJavaCallWithExceptionAndUnwind(CallTargetNode.InvokeKind.Static, targetMethod, arguments); + append(new LoweredDeadEndNode()); + } + + public void emitInvocationTargetException() { + AbstractMergeNode merge = continueWithMerge(invocationTargetExceptionPaths); + ValueNode exception = createPhi(invocationTargetExceptionPaths, merge); + ResolvedJavaMethod throwInvocationTargetException = findMethod(ReflectionAccessorHolder.class, "throwInvocationTargetException", true); + createJavaCallWithExceptionAndUnwind(CallTargetNode.InvokeKind.Static, throwInvocationTargetException, exception); + append(new LoweredDeadEndNode()); + } + + public ValueNode startInstanceOf(ValueNode value, ResolvedJavaType type, boolean nonNull, boolean forException) { + TypeReference typeRef = TypeReference.createTrusted(getAssumptions(), type); + LogicNode condition; + if (nonNull) { + condition = append(InstanceOfNode.create(typeRef, value)); + } else { + condition = append(InstanceOfNode.createAllowNull(typeRef, value, null, null)); + } + + startIf(condition, forException ? BranchProbabilityNode.FAST_PATH_PROFILE : BranchProbabilityNode.LIKELY_PROFILE); + thenPart(); + + return createPiNode(value, StampFactory.object(typeRef, nonNull)); + } + + public AbstractMergeNode continueWithMerge(List predecessors) { + assert predecessors.size() > 0; + if (predecessors.size() == 1) { + lastFixedNode = predecessors.get(0); + return null; + } + + MergeNode merge = graph.add(new MergeNode()); + merge.setStateAfter(getFrameState().create(bci(), merge)); + for (int i = 0; i < predecessors.size(); i++) { + EndNode end = graph.add(new EndNode()); + graph.addAfterFixed(predecessors.get(i), end); + merge.addForwardEnd(end); + } + lastFixedNode = merge; + return merge; + } + + public ValueNode createPhi(List values, AbstractMergeNode merge) { + if (values.size() == 1) { + assert merge == null; + return values.get(0); + } + assert values.size() == merge.forwardEndCount(); + return unique(new ValuePhiNode(StampTool.meetOrNull(values, null), merge, values.toArray(new ValueNode[0]))); + } + + public void fillArgsArray(ValueNode argumentArray, int receiverOffset, ValueNode[] args, Class[] argTypes) { + /* + * The length of the args array at run time must be the same as the length of argTypes. + * Unless the length of argTypes is 0: in that case, null is allowed to be passed in at run + * time too. + */ + LogicNode argsNullCondition = append(IsNullNode.create(argumentArray)); + startIf(argsNullCondition, BranchProbabilityNode.SLOW_PATH_PROFILE); + thenPart(); + if (argTypes.length == 0) { + /* No arguments, so null is an allowed value. */ + } else { + branchToIllegalArgumentException(); + } + elsePart(); + PiNode argumentArrayNonNull = createPiNode(argumentArray, StampFactory.objectNonNull()); + + ValueNode argsLength = append(ArrayLengthNode.create(argumentArrayNonNull, getConstantReflection())); + LogicNode argsLengthCondition = append(IntegerEqualsNode.create(argsLength, ConstantNode.forInt(argTypes.length), NodeView.DEFAULT)); + startIf(argsLengthCondition, BranchProbabilityNode.FAST_PATH_PROFILE); + elsePart(); + branchToIllegalArgumentException(); + thenPart(); + GuardingNode argsBoundsCheckGuard = AbstractBeginNode.prevBegin(lastFixedNode); + + for (int i = 0; i < argTypes.length; i++) { + ValueNode arg = createLoadIndexed(argumentArrayNonNull, i, JavaKind.Object, argsBoundsCheckGuard); + ResolvedJavaType argType = getMetaAccess().lookupJavaType(argTypes[i]); + JavaKind argKind = asKind(argType); + if (argKind.isPrimitive()) { + arg = unboxPrimitive(arg, argKind); + } else { + arg = startInstanceOf(arg, argType, false, true); + elsePart(); + branchToIllegalArgumentException(); + endIf(); + } + args[i + receiverOffset] = arg; + } + + /* IfStructure from argument array length check. */ + endIf(); + /* IfStructure from null check. */ + endIf(); + } + + private ValueNode unboxPrimitive(ValueNode boxedValue, JavaKind argKind) { + startIf(append(IsNullNode.create(boxedValue)), BranchProbabilityNode.SLOW_PATH_PROFILE); + thenPart(); + /* Cannot unbox "null". */ + branchToIllegalArgumentException(); + elsePart(); + PiNode boxedValueNonNull = createPiNode(boxedValue, StampFactory.objectNonNull()); + + List widenedValues = new ArrayList<>(); + List controlFlows = new ArrayList<>(); + List boxedKinds = PRIMITIVE_UNBOXINGS.get(argKind); + for (JavaKind boxedKind : boxedKinds) { + ResolvedJavaType boxedType = getMetaAccess().lookupJavaType(boxedKind.toBoxedJavaClass()); + + ValueNode boxedValueCasted = startInstanceOf(boxedValueNonNull, boxedType, true, false); + ValueNode unboxedValue = createUnboxing(boxedValueCasted, boxedKind); + ValueNode widenedValue = widenPrimitive(unboxedValue, boxedKind, argKind); + widenedValues.add(widenedValue); + /* Merge of all valid control flow edges is appended later. */ + controlFlows.add(lastFixedNode); + lastFixedNode = null; + + /* Continue with check from next loop iteration. */ + elsePart(); + /* + * Since we collect all passing control flow edges manually, we do not need the control + * flow structure anymore. + */ + endIf(); + } + /* Not a type that can be unboxed and widened to the expected primitive kind. */ + branchToIllegalArgumentException(); + + /* Continue with all the passing control flow edges. */ + AbstractMergeNode merge = continueWithMerge(controlFlows); + return createPhi(widenedValues, merge); + } + + private ValueNode widenPrimitive(ValueNode unboxedValue, JavaKind fromKind, JavaKind toKind) { + JavaKind fromStackKind = fromKind.getStackKind(); + JavaKind toStackKind = toKind.getStackKind(); + if (fromStackKind == toStackKind) { + return unboxedValue; + } + + switch (fromStackKind) { + case Int: + switch (toStackKind) { + case Long: + return graph.addOrUniqueWithInputs(SignExtendNode.create(unboxedValue, toStackKind.getBitCount(), NodeView.DEFAULT)); + case Float: + return graph.addOrUniqueWithInputs(FloatConvertNode.create(FloatConvert.I2F, unboxedValue, NodeView.DEFAULT)); + case Double: + return graph.addOrUniqueWithInputs(FloatConvertNode.create(FloatConvert.I2D, unboxedValue, NodeView.DEFAULT)); + default: + throw VMError.shouldNotReachHere(); + } + case Long: + switch (toStackKind) { + case Float: + return graph.addOrUniqueWithInputs(FloatConvertNode.create(FloatConvert.L2F, unboxedValue, NodeView.DEFAULT)); + case Double: + return graph.addOrUniqueWithInputs(FloatConvertNode.create(FloatConvert.L2D, unboxedValue, NodeView.DEFAULT)); + default: + throw VMError.shouldNotReachHere(); + } + case Float: + switch (toStackKind) { + case Double: + return graph.addOrUniqueWithInputs(FloatConvertNode.create(FloatConvert.F2D, unboxedValue, NodeView.DEFAULT)); + default: + throw VMError.shouldNotReachHere(); + } + default: + throw VMError.shouldNotReachHere(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionMethod.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionMethod.java deleted file mode 100644 index 83acbdd47aea..000000000000 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionMethod.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2021, 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.reflect.hosted; - -import org.graalvm.compiler.core.common.type.StampFactory; -import org.graalvm.compiler.core.common.type.TypeReference; -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.PiNode; -import org.graalvm.compiler.nodes.ValueNode; -import org.graalvm.compiler.nodes.calc.IntegerEqualsNode; -import org.graalvm.compiler.nodes.calc.IsNullNode; -import org.graalvm.compiler.nodes.extended.BranchProbabilityNode; -import org.graalvm.compiler.nodes.java.ArrayLengthNode; -import org.graalvm.compiler.nodes.java.InstanceOfNode; - -import com.oracle.svm.core.graal.nodes.LoweredDeadEndNode; -import com.oracle.svm.core.util.ExceptionHelpers; -import com.oracle.svm.hosted.code.NonBytecodeStaticMethod; -import com.oracle.svm.hosted.phases.HostedGraphKit; - -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; - -public abstract class ReflectionMethod extends NonBytecodeStaticMethod { - - public ReflectionMethod(String name, ResolvedJavaMethod prototype) { - super(name, prototype.getDeclaringClass(), prototype.getSignature(), prototype.getConstantPool()); - } - - protected static void throwFailedCast(HostedGraphKit graphKit, ResolvedJavaType expectedType, ValueNode actual) { - ResolvedJavaMethod throwFailedCast = graphKit.findMethod(ExceptionHelpers.class, "throwFailedCast", true); - JavaConstant expected = graphKit.getConstantReflection().asJavaClass(expectedType); - ValueNode expectedNode = graphKit.createConstant(expected, JavaKind.Object); - - graphKit.createJavaCallWithExceptionAndUnwind(CallTargetNode.InvokeKind.Static, throwFailedCast, expectedNode, actual); - graphKit.append(new LoweredDeadEndNode()); - } - - protected static ValueNode createCheckcast(HostedGraphKit graphKit, ValueNode value, ResolvedJavaType type, boolean nonNull) { - TypeReference typeRef = TypeReference.createTrusted(graphKit.getAssumptions(), type); - LogicNode condition; - if (nonNull) { - condition = graphKit.append(InstanceOfNode.create(typeRef, value)); - } else { - condition = graphKit.append(InstanceOfNode.createAllowNull(typeRef, value, null, null)); - } - - graphKit.startIf(condition, BranchProbabilityNode.FAST_PATH_PROFILE); - graphKit.thenPart(); - - PiNode ret = graphKit.createPiNode(value, StampFactory.object(typeRef, nonNull)); - - graphKit.elsePart(); - - throwFailedCast(graphKit, type, value); - - graphKit.endIf(); - - return ret; - } - - protected static void fillArgsArray(HostedGraphKit graphKit, ValueNode argumentArray, int receiverOffset, ValueNode[] args, Class[] argTypes) { - /* - * The length of the args array at run time must be the same as the length of argTypes. - * Unless the length of argTypes is 0: in that case, null is allowed to be passed in at run - * time too. - */ - LogicNode argsNullCondition = graphKit.append(IsNullNode.create(argumentArray)); - graphKit.startIf(argsNullCondition, BranchProbabilityNode.SLOW_PATH_PROFILE); - graphKit.thenPart(); - if (argTypes.length == 0) { - /* No arguments, so null is an allowed value. */ - } else { - throwIllegalArgumentException(graphKit, "wrong number of arguments"); - } - graphKit.elsePart(); - PiNode argumentArrayNonNull = graphKit.createPiNode(argumentArray, StampFactory.objectNonNull()); - - ValueNode argsLength = graphKit.append(ArrayLengthNode.create(argumentArrayNonNull, graphKit.getConstantReflection())); - LogicNode argsLengthCondition = graphKit.append(IntegerEqualsNode.create(argsLength, ConstantNode.forInt(argTypes.length), NodeView.DEFAULT)); - graphKit.startIf(argsLengthCondition, BranchProbabilityNode.FAST_PATH_PROFILE); - graphKit.thenPart(); - - for (int i = 0; i < argTypes.length; i++) { - ValueNode arg = graphKit.createLoadIndexed(argumentArrayNonNull, i, JavaKind.Object); - ResolvedJavaType argType = graphKit.getMetaAccess().lookupJavaType(argTypes[i]); - JavaKind argKind = graphKit.asKind(argType); - if (argKind.isPrimitive()) { - arg = createCheckcast(graphKit, arg, graphKit.getMetaAccess().lookupJavaType(argKind.toBoxedJavaClass()), true); - arg = graphKit.createUnboxing(arg, argKind, graphKit.getMetaAccess()); - } else { - arg = createCheckcast(graphKit, arg, argType, false); - } - - args[i + receiverOffset] = arg; - } - - graphKit.elsePart(); - throwIllegalArgumentException(graphKit, "wrong number of arguments"); - graphKit.endIf(); - - AbstractMergeNode merge = graphKit.endIf(); - if (merge != null) { - /* When argTypes.length == 0 there is an actual merge that needs a state. */ - merge.setStateAfter(graphKit.getFrameState().create(graphKit.bci(), merge)); - } - } - - protected static void throwIllegalArgumentException(HostedGraphKit graphKit, String message) { - ResolvedJavaMethod throwIllegalArgumentException = graphKit.findMethod(ExceptionHelpers.class, "throwIllegalArgumentException", true); - JavaConstant msg = graphKit.getConstantReflection().forString(message); - ValueNode msgNode = graphKit.createConstant(msg, JavaKind.Object); - - graphKit.createJavaCallWithExceptionAndUnwind(CallTargetNode.InvokeKind.Static, throwIllegalArgumentException, msgNode); - graphKit.append(new LoweredDeadEndNode()); - } -} diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectiveInvokeMethod.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectiveInvokeMethod.java index d7e860b3d7ec..d8489dacb74c 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectiveInvokeMethod.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectiveInvokeMethod.java @@ -26,69 +26,93 @@ // Checkstyle: allow reflection -import java.lang.invoke.MethodHandle; +import static org.graalvm.compiler.nodeinfo.NodeCycles.CYCLES_0; +import static org.graalvm.compiler.nodeinfo.NodeSize.SIZE_0; + import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; import org.graalvm.compiler.bytecode.Bytecode; import org.graalvm.compiler.bytecode.ResolvedJavaMethodBytecode; import org.graalvm.compiler.debug.DebugContext; +import org.graalvm.compiler.graph.Node; +import org.graalvm.compiler.graph.NodeClass; +import org.graalvm.compiler.nodeinfo.NodeInfo; +import org.graalvm.compiler.nodes.AbstractMergeNode; import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind; +import org.graalvm.compiler.nodes.ConstantNode; import org.graalvm.compiler.nodes.InvokeWithExceptionNode; +import org.graalvm.compiler.nodes.LogicConstantNode; +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.calc.IntegerEqualsNode; +import org.graalvm.compiler.nodes.extended.BranchProbabilityNode; import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin; +import org.graalvm.compiler.nodes.spi.Canonicalizable; +import org.graalvm.compiler.nodes.spi.CanonicalizerTool; import org.graalvm.compiler.phases.common.inlining.InliningUtil; import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.graal.pointsto.phases.SubstrateIntrinsicGraphBuilder; -import com.oracle.svm.core.invoke.MethodHandleUtils; -import com.oracle.svm.hosted.phases.HostedGraphKit; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.reflect.SubstrateMethodAccessor; +import com.oracle.svm.hosted.code.NonBytecodeStaticMethod; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; -public class ReflectiveInvokeMethod extends ReflectionMethod { +public class ReflectiveInvokeMethod extends NonBytecodeStaticMethod { private final Method method; - private final boolean specialInvoke; - public ReflectiveInvokeMethod(String name, ResolvedJavaMethod prototype, Method method, boolean specialInvoke) { - super(name, prototype); + public ReflectiveInvokeMethod(String name, ResolvedJavaMethod prototype, Method method) { + super(name, prototype.getDeclaringClass(), prototype.getSignature(), prototype.getConstantPool()); this.method = method; - this.specialInvoke = specialInvoke; } + /** + * Builds the graph that is invoked via {@link SubstrateMethodAccessor}. To save code size, both + * the {@link SubstrateMethodAccessor#invoke "regular"} and the + * {@link SubstrateMethodAccessor#invokeSpecial} invocations are done via the same generated + * graph. The first parameter decides which invocation is done. + */ @Override public StructuredGraph buildGraph(DebugContext ctx, ResolvedJavaMethod m, HostedProviders providers, Purpose purpose) { - HostedGraphKit graphKit = new HostedGraphKit(ctx, providers, m); - - ResolvedJavaMethod targetMethod; - ValueNode[] args; - if (!specialInvoke && method.getDeclaringClass() == MethodHandle.class && (method.getName().equals("invoke") || method.getName().equals("invokeExact"))) { - targetMethod = MethodHandleUtils.getThrowUnsupportedOperationException(providers.getMetaAccess()); - args = new ValueNode[0]; + ReflectionGraphKit graphKit = new ReflectionGraphKit(ctx, providers, m); + + ValueNode forceSpecialInvoke = graphKit.loadLocal(0, JavaKind.Int); + ValueNode receiver = graphKit.loadLocal(1, JavaKind.Object); + ValueNode argumentArray = graphKit.loadLocal(2, JavaKind.Object); + /* Clear all locals, so that they are not alive and spilled at method calls. */ + graphKit.getFrameState().clearLocals(); + + ResolvedJavaMethod targetMethod = providers.getMetaAccess().lookupJavaMethod(method); + Class[] argTypes = method.getParameterTypes(); + int receiverOffset = targetMethod.isStatic() ? 0 : 1; + ValueNode[] args = new ValueNode[argTypes.length + receiverOffset]; + if (targetMethod.isStatic()) { + graphKit.emitEnsureInitializedCall(targetMethod.getDeclaringClass()); } else { - targetMethod = providers.getMetaAccess().lookupJavaMethod(method); - Class[] argTypes = method.getParameterTypes(); - - int receiverOffset = targetMethod.isStatic() ? 0 : 1; - args = new ValueNode[argTypes.length + receiverOffset]; - if (targetMethod.isStatic()) { - graphKit.emitEnsureInitializedCall(targetMethod.getDeclaringClass()); - } else { - ValueNode receiver = graphKit.loadLocal(0, JavaKind.Object); - args[0] = createCheckcast(graphKit, receiver, targetMethod.getDeclaringClass(), true); - } - - ValueNode argumentArray = graphKit.loadLocal(1, JavaKind.Object); - fillArgsArray(graphKit, argumentArray, receiverOffset, args, argTypes); + /* + * The specification explicitly demands a NullPointerException and not a + * IllegalArgumentException when the receiver of a non-static method is null + */ + ValueNode receiverNonNull = graphKit.maybeCreateExplicitNullCheck(receiver); + + args[0] = graphKit.startInstanceOf(receiverNonNull, targetMethod.getDeclaringClass(), true, true); + graphKit.elsePart(); + graphKit.branchToIllegalArgumentException(); + graphKit.endIf(); } + graphKit.fillArgsArray(argumentArray, receiverOffset, args, argTypes); + InvokeKind invokeKind; - if (specialInvoke) { - invokeKind = InvokeKind.Special; - } else if (targetMethod.isStatic()) { + if (targetMethod.isStatic()) { invokeKind = InvokeKind.Static; } else if (targetMethod.isInterface()) { invokeKind = InvokeKind.Interface; @@ -98,39 +122,74 @@ public StructuredGraph buildGraph(DebugContext ctx, ResolvedJavaMethod m, Hosted invokeKind = InvokeKind.Virtual; } - InvokeWithExceptionNode invoke = graphKit.createJavaCallWithException(invokeKind, targetMethod, args); - ValueNode ret = invoke; - - graphKit.noExceptionPart(); + List invokes = new ArrayList<>(); + if (invokeKind.isIndirect()) { + /* + * The graphs are generated before the static analysis is finished. At that time, only + * final methods are known to be statically bound. After the static analysis, many more + * methods can be statically bound. To avoid two identical invokeSpecial invokes for + * such cases, we add a CanBeStaticallyBoundNode to the condition. It is replaced with + * "true" when the method can be statically bound after static analysis, effectively + * removing the whole IfNode and the second invoke node. + */ + LogicNode canBeStaticallyBoundCondition = graphKit.getGraph().unique(new CanBeStaticallyBoundNode(targetMethod)); + LogicNode forceSpecialInvokeCondition = graphKit.append(IntegerEqualsNode.create(forceSpecialInvoke, ConstantNode.forBoolean(true, graphKit.getGraph()), NodeView.DEFAULT)); + LogicNode condition = LogicNode.or(canBeStaticallyBoundCondition, forceSpecialInvokeCondition, BranchProbabilityNode.NOT_LIKELY_PROFILE); + graphKit.startIf(condition, BranchProbabilityNode.NOT_LIKELY_PROFILE); + + graphKit.thenPart(); + if (targetMethod.isAbstract()) { + graphKit.branchToIllegalArgumentException(); + } else { + InvokeWithExceptionNode specialInvoke = graphKit.createJavaCallWithException(InvokeKind.Special, targetMethod, args); + invokes.add(specialInvoke); + graphKit.exceptionPart(); + graphKit.branchToInvocationTargetException(graphKit.exceptionObject()); + graphKit.endInvokeWithException(); + } - JavaKind retKind = targetMethod.getSignature().getReturnKind(); - if (retKind == JavaKind.Void) { - ret = graphKit.createObject(null); - } else if (retKind.isPrimitive()) { - ResolvedJavaType boxedRetType = providers.getMetaAccess().lookupJavaType(retKind.toBoxedJavaClass()); - ret = graphKit.createBoxing(ret, retKind, boxedRetType); + graphKit.elsePart(); } - graphKit.createReturn(ret, JavaKind.Object); - + InvokeWithExceptionNode regularInvoke = graphKit.createJavaCallWithException(invokeKind, targetMethod, args); + invokes.add(regularInvoke); graphKit.exceptionPart(); - graphKit.throwInvocationTargetException(graphKit.exceptionObject()); - + graphKit.branchToInvocationTargetException(graphKit.exceptionObject()); graphKit.endInvokeWithException(); - if (invokeKind.isDirect()) { - InvocationPlugin invocationPlugin = providers.getGraphBuilderPlugins().getInvocationPlugins().lookupInvocation(targetMethod); - if (invocationPlugin != null && !invocationPlugin.inlineOnly()) { - /* - * The BytecodeParser applies invocation plugins directly during bytecode parsing. - * We cannot do that because GraphKit is not a GraphBuilderContext. To get as close - * as possible to the BytecodeParser behavior, we create a new graph for the - * intrinsic and inline it immediately. - */ - Bytecode code = new ResolvedJavaMethodBytecode(targetMethod); - StructuredGraph intrinsicGraph = new SubstrateIntrinsicGraphBuilder(graphKit.getOptions(), graphKit.getDebug(), providers, code).buildGraph(invocationPlugin); - if (intrinsicGraph != null) { - InliningUtil.inline(invoke, intrinsicGraph, false, targetMethod); + AbstractMergeNode merge = invokeKind.isIndirect() ? graphKit.endIf() : null; + + JavaKind returnKind = targetMethod.getSignature().getReturnKind(); + ValueNode returnValue; + if (returnKind == JavaKind.Void) { + returnValue = graphKit.createObject(null); + } else { + returnValue = graphKit.createPhi(invokes, merge); + if (returnKind.isPrimitive()) { + ResolvedJavaType boxedRetType = graphKit.getMetaAccess().lookupJavaType(returnKind.toBoxedJavaClass()); + returnValue = graphKit.createBoxing(returnValue, returnKind, boxedRetType); + } + } + graphKit.createReturn(returnValue, JavaKind.Object); + + graphKit.emitIllegalArgumentException(method, receiver, argumentArray); + graphKit.emitInvocationTargetException(); + + for (InvokeWithExceptionNode invoke : invokes) { + if (invoke.getInvokeKind().isDirect()) { + InvocationPlugin invocationPlugin = providers.getGraphBuilderPlugins().getInvocationPlugins().lookupInvocation(targetMethod); + if (invocationPlugin != null && !invocationPlugin.inlineOnly()) { + /* + * The BytecodeParser applies invocation plugins directly during bytecode + * parsing. We cannot do that because GraphKit is not a GraphBuilderContext. To + * get as close as possible to the BytecodeParser behavior, we create a new + * graph for the intrinsic and inline it immediately. + */ + Bytecode code = new ResolvedJavaMethodBytecode(targetMethod); + StructuredGraph intrinsicGraph = new SubstrateIntrinsicGraphBuilder(graphKit.getOptions(), graphKit.getDebug(), providers, code).buildGraph(invocationPlugin); + if (intrinsicGraph != null) { + InliningUtil.inline(invoke, intrinsicGraph, false, targetMethod); + } } } } @@ -138,3 +197,33 @@ public StructuredGraph buildGraph(DebugContext ctx, ResolvedJavaMethod m, Hosted return graphKit.finalizeGraph(); } } + +@NodeInfo(cycles = CYCLES_0, size = SIZE_0) +final class CanBeStaticallyBoundNode extends LogicNode implements Canonicalizable { + public static final NodeClass TYPE = NodeClass.create(CanBeStaticallyBoundNode.class); + + private final ResolvedJavaMethod method; + + protected CanBeStaticallyBoundNode(ResolvedJavaMethod method) { + super(TYPE); + this.method = method; + } + + @Override + public Node canonical(CanonicalizerTool tool) { + if (method.canBeStaticallyBound()) { + /* Success: the static analysis found the method to be statically bound. */ + return LogicConstantNode.tautology(); + } else if (method instanceof SharedMethod) { + /* + * We are after the static analysis and the method can still not be statically bound. + * Eliminate this node, but canonicalizing to "false" keeps the dynamic check in place + * in the generated Graal graph. + */ + return LogicConstantNode.contradiction(); + } else { + /* Static analysis is still running. */ + return this; + } + } +} diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectiveNewInstanceMethod.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectiveNewInstanceMethod.java index f49aa3b85ce6..28b89df01ccb 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectiveNewInstanceMethod.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectiveNewInstanceMethod.java @@ -30,63 +30,61 @@ import org.graalvm.compiler.debug.DebugContext; import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind; +import org.graalvm.compiler.nodes.InvokeWithExceptionNode; import org.graalvm.compiler.nodes.StructuredGraph; import org.graalvm.compiler.nodes.ValueNode; import org.graalvm.compiler.nodes.java.NewInstanceNode; import com.oracle.graal.pointsto.meta.HostedProviders; -import com.oracle.svm.hosted.phases.HostedGraphKit; +import com.oracle.svm.hosted.code.NonBytecodeStaticMethod; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; -public class ReflectiveNewInstanceMethod extends ReflectionMethod { +public class ReflectiveNewInstanceMethod extends NonBytecodeStaticMethod { private final Constructor constructor; public ReflectiveNewInstanceMethod(String name, ResolvedJavaMethod prototype, Constructor constructor) { - super(name, prototype); + super(name, prototype.getDeclaringClass(), prototype.getSignature(), prototype.getConstantPool()); this.constructor = constructor; } @Override - public StructuredGraph buildGraph(DebugContext ctx, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) { - HostedGraphKit graphKit = new HostedGraphKit(ctx, providers, method); + public StructuredGraph buildGraph(DebugContext ctx, ResolvedJavaMethod m, HostedProviders providers, Purpose purpose) { + ReflectionGraphKit graphKit = new ReflectionGraphKit(ctx, providers, m); - ResolvedJavaType type = providers.getMetaAccess().lookupJavaType(constructor.getDeclaringClass()); + ValueNode argumentArray = graphKit.loadLocal(0, JavaKind.Object); + /* Clear all locals, so that they are not alive and spilled at method calls. */ + graphKit.getFrameState().clearLocals(); - graphKit.emitEnsureInitializedCall(type); + ResolvedJavaMethod targetMethod = providers.getMetaAccess().lookupJavaMethod(constructor); + graphKit.emitEnsureInitializedCall(targetMethod.getDeclaringClass()); - ResolvedJavaMethod cons = providers.getMetaAccess().lookupJavaMethod(constructor); Class[] argTypes = constructor.getParameterTypes(); - - ValueNode ret = graphKit.append(createNewInstanceNode(type)); - ValueNode[] args = new ValueNode[argTypes.length + 1]; - args[0] = ret; + args[0] = graphKit.append(createNewInstanceNode(targetMethod.getDeclaringClass())); + graphKit.fillArgsArray(argumentArray, 1, args, argTypes); - ValueNode argumentArray = graphKit.loadLocal(0, JavaKind.Object); - fillArgsArray(graphKit, argumentArray, 1, args, argTypes); + InvokeWithExceptionNode invoke = graphKit.createJavaCallWithException(InvokeKind.Special, targetMethod, args); + graphKit.exceptionPart(); + graphKit.branchToInvocationTargetException(graphKit.exceptionObject()); + graphKit.endInvokeWithException(); + graphKit.createReturn(args[0], JavaKind.Object); - createJavaCall(graphKit, cons, ret, args); + graphKit.emitIllegalArgumentException(constructor, null, argumentArray); + graphKit.emitInvocationTargetException(); + processInvoke(graphKit, invoke); return graphKit.finalizeGraph(); } - protected void createJavaCall(HostedGraphKit graphKit, ResolvedJavaMethod cons, ValueNode ret, ValueNode[] args) { - graphKit.createJavaCallWithException(InvokeKind.Special, cons, args); - - graphKit.noExceptionPart(); - graphKit.createReturn(ret, JavaKind.Object); - - graphKit.exceptionPart(); - graphKit.throwInvocationTargetException(graphKit.exceptionObject()); - - graphKit.endInvokeWithException(); - } - protected ValueNode createNewInstanceNode(ResolvedJavaType type) { return new NewInstanceNode(type, true); } + + @SuppressWarnings("unused") + protected void processInvoke(ReflectionGraphKit graphKit, InvokeWithExceptionNode invoke) { + } }