From 096b17a8ddad75d31628b68c91ec882ed73ee08b Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 23 May 2023 09:05:20 +0200 Subject: [PATCH 01/33] Fix typo in EmptyMemoryAcessProvider. --- .../hosted/ameta/AnalysisConstantReflectionProvider.java | 2 +- ...oryAcessProvider.java => EmptyMemoryAccessProvider.java} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/{EmptyMemoryAcessProvider.java => EmptyMemoryAccessProvider.java} (92%) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java index 32de03cad622..8864e0e2e89c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java @@ -89,7 +89,7 @@ public Boolean constantEquals(Constant x, Constant y) { @Override public MemoryAccessProvider getMemoryAccessProvider() { - return EmptyMemoryAcessProvider.SINGLETON; + return EmptyMemoryAccessProvider.SINGLETON; } private static final Set> BOXING_CLASSES = Set.of(Boolean.class, Byte.class, Short.class, Character.class, Integer.class, Long.class, Float.class, Double.class); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/EmptyMemoryAcessProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/EmptyMemoryAccessProvider.java similarity index 92% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/EmptyMemoryAcessProvider.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/EmptyMemoryAccessProvider.java index 9ab5ed661403..92bcc6121845 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/EmptyMemoryAcessProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/EmptyMemoryAccessProvider.java @@ -29,11 +29,11 @@ import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.MemoryAccessProvider; -public final class EmptyMemoryAcessProvider implements MemoryAccessProvider { +public final class EmptyMemoryAccessProvider implements MemoryAccessProvider { - public static final MemoryAccessProvider SINGLETON = new EmptyMemoryAcessProvider(); + public static final MemoryAccessProvider SINGLETON = new EmptyMemoryAccessProvider(); - private EmptyMemoryAcessProvider() { + private EmptyMemoryAccessProvider() { } @Override From ae5df56e19b802c75e6856362030f266c248abdb Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 1 Jun 2023 17:39:04 +0200 Subject: [PATCH 02/33] Fix dumping for uninitialized CommitAllocationNodes. --- .../nodes/virtual/CommitAllocationNode.java | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/virtual/CommitAllocationNode.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/virtual/CommitAllocationNode.java index 1cb5b2130128..3f05fdb7427f 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/virtual/CommitAllocationNode.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/virtual/CommitAllocationNode.java @@ -123,7 +123,13 @@ public void lower(LoweringTool tool) { @Override public LocationIdentity getKilledLocationIdentity() { - return locks.isEmpty() ? LocationIdentity.init() : LocationIdentity.any(); + if (locks == null) { + return null; + } + if (locks.isEmpty()) { + return LocationIdentity.init(); + } + return LocationIdentity.any(); } @Override @@ -160,6 +166,9 @@ public void virtualize(VirtualizerTool tool) { @Override public Map getDebugProperties(Map map) { Map properties = super.getDebugProperties(map); + if (virtualObjects == null) { + return properties; + } int valuePos = 0; for (int objIndex = 0; objIndex < virtualObjects.size(); objIndex++) { VirtualObjectNode virtual = virtualObjects.get(objIndex); @@ -254,14 +263,9 @@ public void simplify(SimplifierTool tool) { @Override public NodeCycles estimatedNodeCycles() { - List v = getVirtualObjects(); - int fieldWriteCount = 0; - for (int i = 0; i < v.size(); i++) { - VirtualObjectNode node = v.get(i); - if (node == null) { - return CYCLES_UNKNOWN; - } - fieldWriteCount += node.entryCount(); + int fieldWriteCount = getFieldWriteCount(); + if (fieldWriteCount == -1) { + return CYCLES_UNKNOWN; } int rawValueWrites = NodeCycles.compute(WriteNode.TYPE.cycles(), fieldWriteCount).value; int rawValuesTlabBumps = AbstractNewObjectNode.TYPE.cycles().value; @@ -270,20 +274,28 @@ public NodeCycles estimatedNodeCycles() { @Override protected NodeSize dynamicNodeSizeEstimate() { + int fieldWriteCount = getFieldWriteCount(); + if (fieldWriteCount == -1) { + return SIZE_UNKNOWN; + } + int rawValueWrites = NodeSize.compute(WriteNode.TYPE.size(), fieldWriteCount).value; + int rawValuesTlabBumps = AbstractNewObjectNode.TYPE.size().value; + return NodeSize.compute(rawValueWrites + rawValuesTlabBumps); + } + + private int getFieldWriteCount() { List v = getVirtualObjects(); if (v == null) { - return SIZE_UNKNOWN; + return -1; } int fieldWriteCount = 0; for (int i = 0; i < v.size(); i++) { VirtualObjectNode node = v.get(i); if (node == null) { - return SIZE_UNKNOWN; + return -1; } fieldWriteCount += node.entryCount(); } - int rawValueWrites = NodeSize.compute(WriteNode.TYPE.size(), fieldWriteCount).value; - int rawValuesTlabBumps = AbstractNewObjectNode.TYPE.size().value; - return NodeSize.compute(rawValueWrites + rawValuesTlabBumps); + return fieldWriteCount; } } From 84548533417607aa0752f50a051231a99792d5b0 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 25 May 2023 17:26:24 +0200 Subject: [PATCH 03/33] Workaround: use anonymous classes instead of certain lambdas. --- .../JNIThreadLocalPrimitiveArrayViews.java | 17 +--- .../jni/functions/JNIInvocationInterface.java | 7 +- .../target/ReflectionMetadataDecoderImpl.java | 91 ++++++++++++++++--- 3 files changed, 86 insertions(+), 29 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIThreadLocalPrimitiveArrayViews.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIThreadLocalPrimitiveArrayViews.java index ef6e8b5454f6..a27456937f20 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIThreadLocalPrimitiveArrayViews.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIThreadLocalPrimitiveArrayViews.java @@ -24,8 +24,6 @@ */ package com.oracle.svm.core.jni; -import java.util.function.Predicate; - import org.graalvm.word.PointerBase; import com.oracle.svm.core.handles.PrimitiveArrayView; @@ -60,18 +58,11 @@ public static T createArrayViewAndGetAddress(Object arra return createArrayView(array).addressOfArrayElement(0); } - /** - * Removes the first referenced array in the list matching a predicate. - * - * @param p Predicate determining whether to destroy the array reference. - * @return {@code true} if an reference was remove, {@code false} if no array reference matched - * the predicate. - */ - private static boolean destroyFirstArrayView(Predicate p, int mode) { + public static boolean destroyArrayViewByAddress(PointerBase address, int mode) { ReferencedObjectListNode previous = null; ReferencedObjectListNode current = referencedObjectsListHead.get(); while (current != null) { - if (p.test(current)) { + if (current.object.addressOfArrayElement(0) == address) { if (previous != null) { previous.next = current.next; } else { @@ -94,10 +85,6 @@ private static boolean destroyFirstArrayView(Predicate return false; } - public static boolean destroyArrayViewByAddress(PointerBase address, int mode) { - return destroyFirstArrayView(n -> n.object.addressOfArrayElement(0) == address, mode); - } - static int getCount() { int count = 0; ReferencedObjectListNode node = referencedObjectsListHead.get(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java index cde3a38a8de9..99b5084fe07a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java @@ -389,7 +389,12 @@ private static int finishInitialization0(JNIJavaVMPointer vmBuf, JNIEnvironmentP long javaVmId = IsolateUtil.getIsolateID(); javaVmIdPointer.write(WordFactory.pointer(javaVmId)); } - RuntimeSupport.getRuntimeSupport().addTearDownHook(isFirstIsolate -> JNIJavaVMList.removeJavaVM(javaVm)); + RuntimeSupport.getRuntimeSupport().addTearDownHook(new RuntimeSupport.Hook() { + @Override + public void execute(boolean isFirstIsolate) { + JNIJavaVMList.removeJavaVM(javaVm); + } + }); vmBuf.write(javaVm); penv.write(JNIThreadLocalEnvironment.getAddress()); return JNIErrors.JNI_OK(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java index 955060a616ab..5d88462d690f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java @@ -106,13 +106,23 @@ static byte[] getEncoding() { @Override public Field[] parseFields(DynamicHub declaringType, int index, boolean publicOnly) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Field.class, (i) -> (Field) decodeField(reader, DynamicHub.toClass(declaringType), publicOnly, true)); + return decodeArray(reader, Field.class, new Function<>() { + @Override + public Field apply(Integer i) { + return (Field) decodeField(reader, DynamicHub.toClass(declaringType), publicOnly, true); + } + }); } @Override public FieldDescriptor[] parseReachableFields(DynamicHub declaringType, int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, FieldDescriptor.class, (i) -> (FieldDescriptor) decodeField(reader, DynamicHub.toClass(declaringType), false, false)); + return decodeArray(reader, FieldDescriptor.class, new Function<>() { + @Override + public FieldDescriptor apply(Integer i) { + return (FieldDescriptor) decodeField(reader, DynamicHub.toClass(declaringType), false, false); + } + }); } /** @@ -125,13 +135,23 @@ public FieldDescriptor[] parseReachableFields(DynamicHub declaringType, int inde @Override public Method[] parseMethods(DynamicHub declaringType, int index, boolean publicOnly) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Method.class, (i) -> (Method) decodeExecutable(reader, DynamicHub.toClass(declaringType), publicOnly, true, true)); + return decodeArray(reader, Method.class, new Function<>() { + @Override + public Method apply(Integer i) { + return (Method) decodeExecutable(reader, DynamicHub.toClass(declaringType), publicOnly, true, true); + } + }); } @Override public MethodDescriptor[] parseReachableMethods(DynamicHub declaringType, int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, MethodDescriptor.class, (i) -> (MethodDescriptor) decodeExecutable(reader, DynamicHub.toClass(declaringType), false, false, true)); + return decodeArray(reader, MethodDescriptor.class, new Function<>() { + @Override + public MethodDescriptor apply(Integer i) { + return (MethodDescriptor) decodeExecutable(reader, DynamicHub.toClass(declaringType), false, false, true); + } + }); } /** @@ -144,13 +164,23 @@ public MethodDescriptor[] parseReachableMethods(DynamicHub declaringType, int in @Override public Constructor[] parseConstructors(DynamicHub declaringType, int index, boolean publicOnly) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Constructor.class, (i) -> (Constructor) decodeExecutable(reader, DynamicHub.toClass(declaringType), publicOnly, true, false)); + return decodeArray(reader, Constructor.class, new Function<>() { + @Override + public Constructor apply(Integer i) { + return (Constructor) decodeExecutable(reader, DynamicHub.toClass(declaringType), publicOnly, true, false); + } + }); } @Override public ConstructorDescriptor[] parseReachableConstructors(DynamicHub declaringType, int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, ConstructorDescriptor.class, (i) -> (ConstructorDescriptor) decodeExecutable(reader, DynamicHub.toClass(declaringType), false, false, false)); + return decodeArray(reader, ConstructorDescriptor.class, new Function<>() { + @Override + public ConstructorDescriptor apply(Integer i) { + return (ConstructorDescriptor) decodeExecutable(reader, DynamicHub.toClass(declaringType), false, false, false); + } + }); } /** @@ -163,7 +193,12 @@ public ConstructorDescriptor[] parseReachableConstructors(DynamicHub declaringTy @Override public Class[] parseClasses(int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Class.class, (i) -> decodeType(reader)); + return decodeArray(reader, Class.class, new Function<>() { + @Override + public Class apply(Integer i) { + return decodeType(reader); + } + }); } /** @@ -176,7 +211,12 @@ public Class[] parseClasses(int index) { @Override public Target_java_lang_reflect_RecordComponent[] parseRecordComponents(DynamicHub declaringType, int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Target_java_lang_reflect_RecordComponent.class, (i) -> decodeRecordComponent(reader, DynamicHub.toClass(declaringType))); + return decodeArray(reader, Target_java_lang_reflect_RecordComponent.class, new Function<>() { + @Override + public Target_java_lang_reflect_RecordComponent apply(Integer i) { + return decodeRecordComponent(reader, DynamicHub.toClass(declaringType)); + } + }); } /** @@ -189,7 +229,12 @@ public Target_java_lang_reflect_RecordComponent[] parseRecordComponents(DynamicH @Override public Object[] parseObjects(int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Object.class, (i) -> decodeObject(reader)); + return decodeArray(reader, Object.class, new Function<>() { + @Override + public Object apply(Integer i) { + return decodeObject(reader); + } + }); } /** @@ -202,7 +247,12 @@ public Object[] parseObjects(int index) { @Override public Parameter[] parseReflectParameters(Executable executable, byte[] encoding) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(encoding, 0, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Parameter.class, (i) -> decodeReflectParameter(reader, executable, i)); + return decodeArray(reader, Parameter.class, new Function<>() { + @Override + public Parameter apply(Integer i) { + return decodeReflectParameter(reader, executable, i); + } + }); } /** @@ -506,9 +556,19 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class decla String name = isMethod ? decodeName(buf) : null; Object[] parameterTypes; if (complete || hiding || negative) { - parameterTypes = decodeArray(buf, Class.class, (i) -> decodeType(buf)); + parameterTypes = decodeArray(buf, Class.class, new Function<>() { + @Override + public Class apply(Integer i) { + return decodeType(buf); + } + }); } else { - parameterTypes = decodeArray(buf, String.class, (i) -> decodeName(buf)); + parameterTypes = decodeArray(buf, String.class, new Function<>() { + @Override + public String apply(Integer i) { + return decodeName(buf); + } + }); } Class returnType = isMethod && (complete || hiding) ? decodeType(buf) : null; if (!complete) { @@ -536,7 +596,12 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class decla return SubstrateUtil.cast(constructor, Executable.class); } } - Class[] exceptionTypes = decodeArray(buf, Class.class, (i) -> decodeType(buf)); + Class[] exceptionTypes = decodeArray(buf, Class.class, new Function<>() { + @Override + public Class apply(Integer i) { + return decodeType(buf); + } + }); String signature = decodeName(buf); byte[] annotations = decodeByteArray(buf); byte[] parameterAnnotations = decodeByteArray(buf); From 2a23924241495dcf292006be93a071a9330ef430 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 25 May 2023 17:27:02 +0200 Subject: [PATCH 04/33] Intrinsify method handles in inlining before analysis. --- .../graalvm/compiler/nodes/GraphDecoder.java | 93 +++++--- .../graalvm/compiler/nodes/GraphEncoder.java | 32 +-- .../org/graalvm/compiler/nodes/Invokable.java | 15 ++ .../org/graalvm/compiler/nodes/Invoke.java | 14 -- .../replacements/MethodHandlePlugin.java | 21 +- .../MethodHandleWithExceptionPlugin.java | 58 +++++ .../compiler/replacements/PEGraphDecoder.java | 22 +- .../replacements/nodes/MethodHandleNode.java | 89 ++++--- .../nodes/MethodHandleWithExceptionNode.java | 85 +++++++ .../graal/pointsto/meta/AnalysisField.java | 5 +- .../InlineBeforeAnalysisGraphDecoder.java | 47 +++- .../phases/InlineBeforeAnalysisPolicy.java | 13 +- .../SharedConstantReflectionProvider.java | 2 +- .../oracle/svm/core/jdk/VarHandleFeature.java | 15 +- .../Target_java_lang_invoke_MethodHandle.java | 11 + .../ParseOnceRuntimeCompilationFeature.java | 6 +- .../svm/hosted/NativeImageGenerator.java | 9 +- .../AnalysisConstantReflectionProvider.java | 8 + .../AnalysisMethodHandleAccessProvider.java | 84 +++++++ .../svm/hosted/code/InliningUtilities.java | 3 +- .../StaticFinalFieldFoldingFeature.java | 5 +- .../InlineBeforeAnalysisPolicyImpl.java | 5 +- .../InlineBeforeAnalysisPolicyUtils.java | 219 +++++++++++++++--- ...trinsifyMethodHandlesInvocationPlugin.java | 5 + .../UnsafeAutomaticSubstitutionProcessor.java | 2 +- 25 files changed, 689 insertions(+), 179 deletions(-) create mode 100644 compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/MethodHandleWithExceptionPlugin.java create mode 100644 compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleWithExceptionNode.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisMethodHandleAccessProvider.java diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/GraphDecoder.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/GraphDecoder.java index d8664c2eb392..80045e19805b 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/GraphDecoder.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/GraphDecoder.java @@ -74,6 +74,7 @@ import org.graalvm.compiler.nodes.spi.Canonicalizable; import org.graalvm.compiler.nodes.spi.CanonicalizerTool; import org.graalvm.compiler.options.OptionValues; +import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; import jdk.vm.ci.code.Architecture; import jdk.vm.ci.meta.Assumptions; @@ -446,31 +447,21 @@ public int hashCode() { } } - /** - * Additional information encoded for {@link Invoke} nodes to allow method inlining without - * decoding the frame state and successors beforehand. - */ - protected static class InvokeData { - public final Invoke invoke; + protected static class InvokableData { + public final T invoke; public final ResolvedJavaType contextType; - public final int invokeOrderId; - public final int callTargetOrderId; + public final int orderId; public final int stateAfterOrderId; public final int nextOrderId; - public final int exceptionOrderId; public final int exceptionStateOrderId; public final int exceptionNextOrderId; - public JavaConstant constantReceiver; - public CallTargetNode callTarget; - public FixedWithNextNode invokePredecessor; - protected InvokeData(Invoke invoke, ResolvedJavaType contextType, int invokeOrderId, int callTargetOrderId, int stateAfterOrderId, int nextOrderId, + protected InvokableData(T invoke, ResolvedJavaType contextType, int orderId, int stateAfterOrderId, int nextOrderId, int exceptionOrderId, int exceptionStateOrderId, int exceptionNextOrderId) { this.invoke = invoke; this.contextType = contextType; - this.invokeOrderId = invokeOrderId; - this.callTargetOrderId = callTargetOrderId; + this.orderId = orderId; this.stateAfterOrderId = stateAfterOrderId; this.nextOrderId = nextOrderId; this.exceptionOrderId = exceptionOrderId; @@ -479,6 +470,31 @@ protected InvokeData(Invoke invoke, ResolvedJavaType contextType, int invokeOrde } } + /** + * Additional information encoded for {@link Invoke} nodes to allow method inlining without + * decoding the frame state and successors beforehand. + */ + protected static class InvokeData extends InvokableData { + static InvokeData createFrom(InvokableData from, int callTargetOrderId, boolean intrinsifiedMethodHandle) { + return new InvokeData(from.invoke, from.contextType, from.orderId, callTargetOrderId, intrinsifiedMethodHandle, + from.stateAfterOrderId, from.nextOrderId, from.exceptionOrderId, from.exceptionStateOrderId, from.exceptionNextOrderId); + } + + public final int callTargetOrderId; + public final boolean intrinsifiedMethodHandle; + + public JavaConstant constantReceiver; + public CallTargetNode callTarget; + public FixedWithNextNode invokePredecessor; + + public InvokeData(Invoke invoke, ResolvedJavaType contextType, int invokeOrderId, int callTargetOrderId, boolean intrinsifiedMethodHandle, + int stateAfterOrderId, int nextOrderId, int exceptionOrderId, int exceptionStateOrderId, int exceptionNextOrderId) { + super(invoke, contextType, invokeOrderId, stateAfterOrderId, nextOrderId, exceptionOrderId, exceptionStateOrderId, exceptionNextOrderId); + this.callTargetOrderId = callTargetOrderId; + this.intrinsifiedMethodHandle = intrinsifiedMethodHandle; + } + } + /** * A node that is created during {@link LoopExplosionKind#MERGE_EXPLODE loop explosion} that can * later be replaced by a ProxyNode if {@link LoopDetector loop detection} finds out that the @@ -903,6 +919,9 @@ protected LoopScope processNextNode(MethodScope methodScope, LoopScope loopScope } else if (node instanceof Invoke) { InvokeData invokeData = readInvokeData(methodScope, nodeOrderId, (Invoke) node); resultScope = handleInvoke(methodScope, loopScope, invokeData); + } else if (node instanceof MethodHandleWithExceptionNode methodHandle) { + InvokableData invokableData = readInvokableData(methodScope, nodeOrderId, methodHandle); + resultScope = handleMethodHandle(methodScope, loopScope, invokableData); } else if (node instanceof ReturnNode || node instanceof UnwindNode) { methodScope.returnAndUnwindNodes.add((ControlSinkNode) node); } else { @@ -923,23 +942,32 @@ protected LoopScope processNextNode(MethodScope methodScope, LoopScope loopScope } } - protected InvokeData readInvokeData(MethodScope methodScope, int invokeOrderId, Invoke invoke) { + protected LoopScope handleMethodHandle(MethodScope methodScope, LoopScope loopScope, InvokableData invokableData) { + appendInvokable(methodScope, loopScope, invokableData); + return loopScope; + } + + protected InvokableData readInvokableData(MethodScope methodScope, int orderId, T node) { ResolvedJavaType contextType = (ResolvedJavaType) readObject(methodScope); - int callTargetOrderId = readOrderId(methodScope); int stateAfterOrderId = readOrderId(methodScope); int nextOrderId = readOrderId(methodScope); - if (invoke instanceof InvokeWithExceptionNode) { + if (node instanceof WithExceptionNode) { int exceptionOrderId = readOrderId(methodScope); int exceptionStateOrderId = readOrderId(methodScope); int exceptionNextOrderId = readOrderId(methodScope); - return new InvokeData(invoke, contextType, invokeOrderId, callTargetOrderId, stateAfterOrderId, nextOrderId, exceptionOrderId, exceptionStateOrderId, - exceptionNextOrderId); + return new InvokableData<>(node, contextType, orderId, stateAfterOrderId, nextOrderId, exceptionOrderId, exceptionStateOrderId, exceptionNextOrderId); } else { - return new InvokeData(invoke, contextType, invokeOrderId, callTargetOrderId, stateAfterOrderId, nextOrderId, -1, -1, -1); + return new InvokableData<>(node, contextType, orderId, stateAfterOrderId, nextOrderId, -1, -1, -1); } } + protected InvokeData readInvokeData(MethodScope methodScope, int invokeOrderId, Invoke invoke) { + int callTargetOrderId = readOrderId(methodScope); + InvokableData invokableData = readInvokableData(methodScope, invokeOrderId, invoke); + return InvokeData.createFrom(invokableData, callTargetOrderId, false); + } + /** * {@link Invoke} nodes do not have the {@link CallTargetNode}, {@link FrameState}, and * successors encoded. Instead, this information is provided separately to allow method inlining @@ -963,15 +991,21 @@ protected void appendInvoke(MethodScope methodScope, LoopScope loopScope, Invoke } else { ((InvokeNode) invokeData.invoke).setCallTarget(callTarget); } + appendInvokable(methodScope, loopScope, invokeData); + } + private void appendInvokable(MethodScope methodScope, LoopScope loopScope, InvokableData invokeData) { if (invokeData.invoke.stateAfter() == null) { invokeData.invoke.setStateAfter((FrameState) ensureNodeCreated(methodScope, loopScope, invokeData.stateAfterOrderId)); } - assert invokeData.invoke.stateDuring() == null : "stateDuring is not used in high tier graphs"; + assert !(invokeData.invoke instanceof Invoke inv && inv.stateDuring() != null) : "stateDuring is not used in high tier graphs"; - invokeData.invoke.setNext(makeStubNode(methodScope, loopScope, invokeData.nextOrderId)); - if (invokeData.invoke instanceof InvokeWithExceptionNode) { - ((InvokeWithExceptionNode) invokeData.invoke).setExceptionEdge((AbstractBeginNode) makeStubNode(methodScope, loopScope, invokeData.exceptionOrderId)); + FixedNode next = makeStubNode(methodScope, loopScope, invokeData.nextOrderId); + if (invokeData.invoke instanceof WithExceptionNode withException) { + withException.setNext((AbstractBeginNode) next); + withException.setExceptionEdge((AbstractBeginNode) makeStubNode(methodScope, loopScope, invokeData.exceptionOrderId)); + } else { + ((FixedWithNextNode) invokeData.invoke).setNext(next); } } @@ -1705,18 +1739,17 @@ protected FixedNode makeStubNode(MethodScope methodScope, LoopScope loopScope, i } protected static boolean skipDirectEdge(Node node, Edges edges, int index) { - if (node instanceof Invoke) { - assert node instanceof InvokeNode || node instanceof InvokeWithExceptionNode : "The only two Invoke node classes. Got " + node.getClass(); + if (node instanceof Invoke || node instanceof MethodHandleWithExceptionNode) { + assert node instanceof InvokeNode || node instanceof InvokeWithExceptionNode || node instanceof MethodHandleWithExceptionNode : "The only supported node classes. Got " + node.getClass(); if (edges.type() == Edges.Type.Successors) { - assert edges.getCount() == (node instanceof InvokeWithExceptionNode ? 2 - : 1) : "InvokeNode has one successor (next); InvokeWithExceptionNode has two successors (next, exceptionEdge)"; + assert edges.getCount() == (node instanceof WithExceptionNode ? 2 : 1) : "Base Invokable has one successor (next); WithExceptionNode has two successors (next, exceptionEdge)"; return true; } else { assert edges.type() == Edges.Type.Inputs; if (edges.getType(index) == CallTargetNode.class) { return true; } else if (edges.getType(index) == FrameState.class) { - assert edges.get(node, index) == null || edges.get(node, index) == ((Invoke) node).stateAfter() : "Only stateAfter can be a FrameState during encoding"; + assert edges.get(node, index) == null || edges.get(node, index) == ((StateSplit) node).stateAfter() : "Only stateAfter can be a FrameState during encoding"; return true; } } diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/GraphEncoder.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/GraphEncoder.java index dc829a1fd746..7291a046b19f 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/GraphEncoder.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/GraphEncoder.java @@ -47,6 +47,7 @@ import org.graalvm.compiler.graph.iterators.NodeIterable; import org.graalvm.compiler.nodes.EncodedGraph.EncodedNodeReference; import org.graalvm.compiler.nodes.java.ExceptionObjectNode; +import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; import jdk.vm.ci.code.Architecture; @@ -216,8 +217,8 @@ public void prepare(StructuredGraph graph) { addObject(nodeClass.getData().get(node, i)); } } - if (node instanceof Invoke) { - addObject(((Invoke) node).getContextType()); + if (node instanceof Invoke || node instanceof MethodHandleWithExceptionNode) { + addObject(((Invokable) node).getContextType()); } } } @@ -313,19 +314,24 @@ protected int encode(StructuredGraph graph, Iterable nodeR writeOrderId(proxy, nodeOrder); } - } else if (node instanceof Invoke) { - Invoke invoke = (Invoke) node; - assert invoke.stateDuring() == null : "stateDuring is not used in high-level graphs"; + } else if (node instanceof Invoke || node instanceof MethodHandleWithExceptionNode) { + Node next; + if (node instanceof Invoke invoke) { + assert invoke.stateDuring() == null : "stateDuring is not used in high-level graphs"; + writeOrderId(invoke.callTarget(), nodeOrder); + next = invoke.next(); + } else { + next = ((MethodHandleWithExceptionNode) node).next(); + } - writeObjectId(invoke.getContextType()); - writeOrderId(invoke.callTarget(), nodeOrder); - writeOrderId(invoke.stateAfter(), nodeOrder); - writeOrderId(invoke.next(), nodeOrder); - if (invoke instanceof InvokeWithExceptionNode) { - InvokeWithExceptionNode invokeWithExcpetion = (InvokeWithExceptionNode) invoke; - ExceptionObjectNode exceptionEdge = (ExceptionObjectNode) invokeWithExcpetion.exceptionEdge(); + Invokable inv = (Invokable) node; + writeObjectId(inv.getContextType()); + writeOrderId(((StateSplit) inv).stateAfter(), nodeOrder); + writeOrderId(next, nodeOrder); + if (inv instanceof WithExceptionNode withException) { + ExceptionObjectNode exceptionEdge = (ExceptionObjectNode) withException.exceptionEdge(); - writeOrderId(invokeWithExcpetion.exceptionEdge(), nodeOrder); + writeOrderId(withException.exceptionEdge(), nodeOrder); writeOrderId(exceptionEdge.stateAfter(), nodeOrder); writeOrderId(exceptionEdge.next(), nodeOrder); } diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/Invokable.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/Invokable.java index 196b43256d66..1506145f1599 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/Invokable.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/Invokable.java @@ -28,6 +28,7 @@ import org.graalvm.compiler.graph.Node; import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; /** * A marker interface for nodes that represent calls to other methods. @@ -42,6 +43,20 @@ public interface Invokable extends DeoptBciSupplier { */ ResolvedJavaMethod getContextMethod(); + /** + * Returns the {@linkplain ResolvedJavaType type} from which this invoke is executed. This is + * the declaring type of the caller method. + * + * @return the type from which this invoke is executed. + */ + default ResolvedJavaType getContextType() { + ResolvedJavaMethod contextMethod = getContextMethod(); + if (contextMethod == null) { + return null; + } + return contextMethod.getDeclaringClass(); + } + /** * Returns the receiver cast to {@link FixedNode}, or null if this invokable is a placeholder. */ diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/Invoke.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/Invoke.java index aa4ba63cddd0..28cf66556de7 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/Invoke.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/Invoke.java @@ -110,20 +110,6 @@ default ResolvedJavaMethod getContextMethod() { return state.getMethod(); } - /** - * Returns the {@linkplain ResolvedJavaType type} from which this invoke is executed. This is - * the declaring type of the caller method. - * - * @return the type from which this invoke is executed. - */ - default ResolvedJavaType getContextType() { - ResolvedJavaMethod contextMethod = getContextMethod(); - if (contextMethod == null) { - return null; - } - return contextMethod.getDeclaringClass(); - } - @Override default void computeStateDuring(FrameState stateAfter) { FrameState newStateDuring = stateAfter.duplicateModifiedDuringCall(bci(), asNode().getStackKind()); diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/MethodHandlePlugin.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/MethodHandlePlugin.java index 94d80e8bf29b..11c52d3dba7f 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/MethodHandlePlugin.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/MethodHandlePlugin.java @@ -26,6 +26,7 @@ import static org.graalvm.compiler.core.common.GraalOptions.MaximumRecursiveInlining; +import org.graalvm.compiler.core.common.type.Stamp; import org.graalvm.compiler.core.common.type.StampPair; import org.graalvm.compiler.graph.NodeInputList; import org.graalvm.compiler.nodes.CallTargetNode; @@ -35,7 +36,8 @@ import org.graalvm.compiler.nodes.ValueNode; import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext; import org.graalvm.compiler.nodes.graphbuilderconf.NodePlugin; -import org.graalvm.compiler.replacements.nodes.MacroNode.MacroParams; +import org.graalvm.compiler.replacements.nodes.MacroInvokable; +import org.graalvm.compiler.replacements.nodes.MacroNode; import org.graalvm.compiler.replacements.nodes.MethodHandleNode; import jdk.vm.ci.meta.JavaKind; @@ -52,6 +54,15 @@ public MethodHandlePlugin(MethodHandleAccessProvider methodHandleAccess, boolean this.safeForDeoptimization = safeForDeoptimization; } + protected Invoke createInvoke(CallTargetNode callTarget, int bci, Stamp stamp) { + return new InvokeNode(callTarget, bci, stamp); + } + + protected MacroInvokable createMethodHandleNode(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args, + IntrinsicMethod intrinsicMethod, InvokeKind invokeKind, StampPair invokeReturnStamp) { + return new MethodHandleNode(intrinsicMethod, MacroNode.MacroParams.of(invokeKind, b.getMethod(), method, b.bci(), invokeReturnStamp, args)); + } + @Override public boolean handleInvoke(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args) { IntrinsicMethod intrinsicMethod = methodHandleAccess.lookupMethodHandleIntrinsic(method); @@ -67,13 +78,13 @@ public T add(T node) { return b.add(node); } }; - InvokeNode invoke = MethodHandleNode.tryResolveTargetInvoke(adder, methodHandleAccess, intrinsicMethod, method, b.bci(), invokeReturnStamp, args); + Invoke invoke = MethodHandleNode.tryResolveTargetInvoke(adder, this::createInvoke, methodHandleAccess, intrinsicMethod, method, b.bci(), invokeReturnStamp, args); if (invoke == null) { - MethodHandleNode methodHandleNode = new MethodHandleNode(intrinsicMethod, MacroParams.of(invokeKind, b.getMethod(), method, b.bci(), invokeReturnStamp, args)); + MacroInvokable methodHandleNode = createMethodHandleNode(b, method, args, intrinsicMethod, invokeKind, invokeReturnStamp); if (invokeReturnStamp.getTrustedStamp().getStackKind() == JavaKind.Void) { - b.add(methodHandleNode); + b.add(methodHandleNode.asNode()); } else { - b.addPush(invokeReturnStamp.getTrustedStamp().getStackKind(), methodHandleNode); + b.addPush(invokeReturnStamp.getTrustedStamp().getStackKind(), methodHandleNode.asNode()); } } else { CallTargetNode callTarget = invoke.callTarget(); diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/MethodHandleWithExceptionPlugin.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/MethodHandleWithExceptionPlugin.java new file mode 100644 index 000000000000..af8250416375 --- /dev/null +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/MethodHandleWithExceptionPlugin.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023, 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 org.graalvm.compiler.replacements; + +import org.graalvm.compiler.core.common.type.Stamp; +import org.graalvm.compiler.core.common.type.StampPair; +import org.graalvm.compiler.nodes.CallTargetNode; +import org.graalvm.compiler.nodes.Invoke; +import org.graalvm.compiler.nodes.InvokeWithExceptionNode; +import org.graalvm.compiler.nodes.ValueNode; +import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext; +import org.graalvm.compiler.replacements.nodes.MacroInvokable; +import org.graalvm.compiler.replacements.nodes.MacroNode; +import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; + +import jdk.vm.ci.meta.MethodHandleAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +public class MethodHandleWithExceptionPlugin extends MethodHandlePlugin { + public MethodHandleWithExceptionPlugin(MethodHandleAccessProvider methodHandleAccess, boolean safeForDeoptimization) { + super(methodHandleAccess, safeForDeoptimization); + } + + @Override + protected Invoke createInvoke(CallTargetNode callTarget, int bci, Stamp stamp) { + InvokeWithExceptionNode invoke = new InvokeWithExceptionNode(callTarget, null, bci); + invoke.setStamp(stamp); + return invoke; + } + + @Override + protected MacroInvokable createMethodHandleNode(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args, + MethodHandleAccessProvider.IntrinsicMethod intrinsicMethod, CallTargetNode.InvokeKind invokeKind, StampPair invokeReturnStamp) { + return new MethodHandleWithExceptionNode(intrinsicMethod, MacroNode.MacroParams.of(invokeKind, b.getMethod(), method, b.bci(), invokeReturnStamp, args)); + } +} diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java index 7b0192c0e910..603748f28b2a 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java @@ -944,20 +944,22 @@ private static RuntimeException tooManyLoopExplosionIterations(PEMethodScope met @Override protected LoopScope handleInvoke(MethodScope s, LoopScope loopScope, InvokeData invokeData) { - PEMethodScope methodScope = (PEMethodScope) s; /* * Decode the call target, but do not add it to the graph yet. This avoids adding usages for * all the arguments, which are expensive to remove again when we can inline the method. */ assert invokeData.invoke.callTarget() == null : "callTarget edge is ignored during decoding of Invoke"; - CallTargetNode callTarget = (CallTargetNode) decodeFloatingNode(methodScope, loopScope, invokeData.callTargetOrderId); - invokeData.callTarget = callTarget; - if (callTarget instanceof MethodCallTargetNode) { - MethodCallTargetNode methodCall = (MethodCallTargetNode) callTarget; + invokeData.callTarget = (CallTargetNode) decodeFloatingNode(s, loopScope, invokeData.callTargetOrderId); + return handleInvokeWithCallTarget((PEMethodScope) s, loopScope, invokeData); + } + + protected LoopScope handleInvokeWithCallTarget(PEMethodScope methodScope, LoopScope loopScope, InvokeData invokeData) { + CallTargetNode callTarget = invokeData.callTarget; + if (callTarget instanceof MethodCallTargetNode methodCall) { if (methodCall.invokeKind().hasReceiver()) { invokeData.constantReceiver = methodCall.arguments().get(0).asJavaConstant(); } - callTarget = trySimplifyCallTarget(methodScope, invokeData, (MethodCallTargetNode) callTarget); + callTarget = trySimplifyCallTarget(methodScope, invokeData, methodCall); ResolvedJavaMethod targetMethod = callTarget.targetMethod(); if (forceLink && targetMethod.hasBytecodes() && targetMethod.getCode() == null && !targetMethod.getDeclaringClass().isLinked()) { targetMethod.getDeclaringClass().link(); @@ -977,7 +979,9 @@ protected void handleNonInlinedInvoke(MethodScope methodScope, LoopScope loopSco /* We know that we need an invoke, so now we can add the call target to the graph. */ graph.add(callTarget); - registerNode(loopScope, invokeData.callTargetOrderId, callTarget, false, false); + if (invokeData.callTargetOrderId > 0) { + registerNode(loopScope, invokeData.callTargetOrderId, callTarget, false, false); + } appendInvoke(methodScope, loopScope, invokeData, callTarget); } @@ -1100,7 +1104,7 @@ protected boolean tryInvocationPlugin(PEMethodScope methodScope, LoopScope loopS BytecodeFrame.isPlaceholderBci(((DeoptBciSupplier) graphBuilderContext.lastInstr).bci())) { ((DeoptBciSupplier) graphBuilderContext.lastInstr).setBci(invokeData.invoke.bci()); } - registerNode(loopScope, invokeData.invokeOrderId, graphBuilderContext.pushedNode, true, true); + registerNode(loopScope, invokeData.orderId, graphBuilderContext.pushedNode, true, true); invoke.asNode().replaceAtUsages(graphBuilderContext.pushedNode); BeginNode begin = graphBuilderContext.lastInstr instanceof BeginNode ? (BeginNode) graphBuilderContext.lastInstr : null; FixedNode afterInvoke = nodeAfterInvoke(methodScope, loopScope, invokeData, begin); @@ -1362,7 +1366,7 @@ protected void finishInlining(MethodScope is) { * Use the handles that we have on the return value and the exception to update the * orderId->Node table. */ - registerNode(loopScope, invokeData.invokeOrderId, returnValue, true, true); + registerNode(loopScope, invokeData.orderId, returnValue, true, true); if (invoke instanceof InvokeWithExceptionNode) { registerNode(loopScope, invokeData.exceptionOrderId, exceptionValue, true, true); } diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleNode.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleNode.java index 3d5f23b7a8c8..627151b38df7 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleNode.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleNode.java @@ -30,13 +30,12 @@ import java.lang.invoke.MethodHandle; import java.util.Arrays; +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.core.common.type.TypeReference; import org.graalvm.compiler.debug.GraalError; import org.graalvm.compiler.graph.NodeClass; -import org.graalvm.compiler.nodes.spi.Simplifiable; -import org.graalvm.compiler.nodes.spi.SimplifierTool; import org.graalvm.compiler.nodeinfo.NodeInfo; import org.graalvm.compiler.nodes.CallTargetNode; import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind; @@ -44,6 +43,7 @@ import org.graalvm.compiler.nodes.FixedNode; import org.graalvm.compiler.nodes.FixedWithNextNode; import org.graalvm.compiler.nodes.GuardNode; +import org.graalvm.compiler.nodes.Invoke; import org.graalvm.compiler.nodes.InvokeNode; import org.graalvm.compiler.nodes.LogicNode; import org.graalvm.compiler.nodes.NodeView; @@ -55,6 +55,8 @@ import org.graalvm.compiler.nodes.extended.ValueAnchorNode; import org.graalvm.compiler.nodes.java.InstanceOfNode; import org.graalvm.compiler.nodes.java.MethodCallTargetNode; +import org.graalvm.compiler.nodes.spi.Simplifiable; +import org.graalvm.compiler.nodes.spi.SimplifierTool; import org.graalvm.compiler.nodes.type.StampTool; import org.graalvm.compiler.nodes.util.GraphUtil; @@ -93,27 +95,30 @@ public MethodHandleNode(IntrinsicMethod intrinsicMethod, MacroParams p) { * @param methodHandleAccess objects for accessing the implementation internals of a * {@link MethodHandle} * @param intrinsicMethod denotes the intrinsifiable {@link MethodHandle} method being processed - * @param bci the BCI of the original {@link MethodHandle} call * @param returnStamp return stamp of the original {@link MethodHandle} call * @param arguments arguments to the original {@link MethodHandle} call * @return a more direct invocation derived from the {@link MethodHandle} call or null */ - public static InvokeNode tryResolveTargetInvoke(GraphAdder adder, MethodHandleAccessProvider methodHandleAccess, IntrinsicMethod intrinsicMethod, - ResolvedJavaMethod original, int bci, - StampPair returnStamp, ValueNode... arguments) { + public static T tryResolveTargetInvoke(GraphAdder adder, InvokeFactory factory, MethodHandleAccessProvider methodHandleAccess, + IntrinsicMethod intrinsicMethod, ResolvedJavaMethod original, int bci, StampPair returnStamp, ValueNode... arguments) { switch (intrinsicMethod) { case INVOKE_BASIC: - return getInvokeBasicTarget(adder, intrinsicMethod, methodHandleAccess, original, bci, returnStamp, arguments); + return getInvokeBasicTarget(adder, factory, intrinsicMethod, methodHandleAccess, original, bci, returnStamp, arguments); case LINK_TO_STATIC: case LINK_TO_SPECIAL: case LINK_TO_VIRTUAL: case LINK_TO_INTERFACE: - return getLinkToTarget(adder, intrinsicMethod, methodHandleAccess, original, bci, returnStamp, arguments); + return getLinkToTarget(adder, factory, intrinsicMethod, methodHandleAccess, original, bci, returnStamp, arguments); default: throw GraalError.shouldNotReachHereUnexpectedValue(intrinsicMethod); // ExcludeFromJacocoGeneratedReport } } + @FunctionalInterface + public interface InvokeFactory { + T create(CallTargetNode callTarget, int bci, Stamp stamp); + } + /** * A simple utility class for adding nodes to the graph when building a MethodHandle invoke. */ @@ -128,7 +133,6 @@ public GraphAdder(StructuredGraph graph) { * Call {@link StructuredGraph#addOrUnique(org.graalvm.compiler.graph.Node)} on {@code node} * and link any {@link FixedWithNextNode}s into the current control flow. * - * @param node * @return the newly added node */ public abstract T add(T node); @@ -146,25 +150,29 @@ public Assumptions getAssumptions() { } } - @Override - public void simplify(SimplifierTool tool) { - MethodHandleAccessProvider methodHandleAccess = tool.getConstantReflection().getMethodHandleAccess(); - ValueNode[] argumentsArray = arguments.toArray(new ValueNode[arguments.size()]); - - final FixedNode before = this; - GraphAdder adder = new GraphAdder(graph()) { + static GraphAdder getGraphAdderBeforeNode(FixedNode before) { + StructuredGraph graph = before.graph(); + return new GraphAdder(graph) { @Override public T add(T node) { - T added = graph().addOrUnique(node); + T added = graph.addOrUnique(node); if (added instanceof FixedWithNextNode) { - graph().addBeforeFixed(before, (FixedWithNextNode) added); + graph.addBeforeFixed(before, (FixedWithNextNode) added); } return added; } }; - InvokeNode invoke = tryResolveTargetInvoke(adder, methodHandleAccess, intrinsicMethod, targetMethod, bci, returnStamp, argumentsArray); + } + + @Override + public void simplify(SimplifierTool tool) { + MethodHandleAccessProvider methodHandleAccess = tool.getConstantReflection().getMethodHandleAccess(); + ValueNode[] argumentsArray = arguments.toArray(new ValueNode[arguments.size()]); + + InvokeNode invoke = tryResolveTargetInvoke(getGraphAdderBeforeNode(this), InvokeNode::new, methodHandleAccess, intrinsicMethod, targetMethod, bci, returnStamp, argumentsArray); if (invoke != null) { assert invoke.graph() == null; + invoke.setNodeSourcePosition(getNodeSourcePosition()); invoke = graph().addOrUniqueWithInputs(invoke); invoke.setStateAfter(stateAfter()); FixedNode currentNext = next(); @@ -196,18 +204,14 @@ private static ValueNode getMemberName(ValueNode[] arguments) { * Used for the MethodHandle.invokeBasic method (the {@link IntrinsicMethod#INVOKE_BASIC } * method) to get the target {@link InvokeNode} if the method handle receiver is constant. * - * @param adder - * * @return invoke node for the {@link java.lang.invoke.MethodHandle} target */ - private static InvokeNode getInvokeBasicTarget(GraphAdder adder, IntrinsicMethod intrinsicMethod, MethodHandleAccessProvider methodHandleAccess, - ResolvedJavaMethod original, - int bci, - StampPair returnStamp, ValueNode[] arguments) { + private static T getInvokeBasicTarget(GraphAdder adder, InvokeFactory factory, IntrinsicMethod intrinsicMethod, + MethodHandleAccessProvider methodHandleAccess, ResolvedJavaMethod original, int bci, StampPair returnStamp, ValueNode[] arguments) { ValueNode methodHandleNode = getReceiver(arguments); if (methodHandleNode.isConstant()) { - return getTargetInvokeNode(adder, intrinsicMethod, methodHandleAccess, bci, returnStamp, arguments, methodHandleAccess.resolveInvokeBasicTarget(methodHandleNode.asJavaConstant(), true), - original); + return getTargetInvokeNode(adder, factory, intrinsicMethod, methodHandleAccess, bci, returnStamp, arguments, + methodHandleAccess.resolveInvokeBasicTarget(methodHandleNode.asJavaConstant(), true), original); } return null; } @@ -218,17 +222,14 @@ private static InvokeNode getInvokeBasicTarget(GraphAdder adder, IntrinsicMethod * {@link IntrinsicMethod#LINK_TO_INTERFACE} methods) to get the target {@link InvokeNode} if * the member name argument is constant. * - * @param adder - * * @return invoke node for the member name target */ - private static InvokeNode getLinkToTarget(GraphAdder adder, IntrinsicMethod intrinsicMethod, MethodHandleAccessProvider methodHandleAccess, - ResolvedJavaMethod original, - int bci, - StampPair returnStamp, ValueNode[] arguments) { + private static T getLinkToTarget(GraphAdder adder, InvokeFactory factory, IntrinsicMethod intrinsicMethod, + MethodHandleAccessProvider methodHandleAccess, ResolvedJavaMethod original, int bci, StampPair returnStamp, ValueNode[] arguments) { ValueNode memberNameNode = getMemberName(arguments); if (memberNameNode.isConstant()) { - return getTargetInvokeNode(adder, intrinsicMethod, methodHandleAccess, bci, returnStamp, arguments, methodHandleAccess.resolveLinkToTarget(memberNameNode.asJavaConstant()), original); + return getTargetInvokeNode(adder, factory, intrinsicMethod, methodHandleAccess, bci, returnStamp, arguments, + methodHandleAccess.resolveLinkToTarget(memberNameNode.asJavaConstant()), original); } return null; } @@ -237,14 +238,11 @@ private static InvokeNode getLinkToTarget(GraphAdder adder, IntrinsicMethod intr * Helper function to get the {@link InvokeNode} for the targetMethod of a * java.lang.invoke.MemberName. * - * @param adder * @param target the target, already loaded from the member name node - * * @return invoke node for the member name target */ - private static InvokeNode getTargetInvokeNode(GraphAdder adder, IntrinsicMethod intrinsicMethod, MethodHandleAccessProvider methodHandleAccess, int bci, StampPair returnStamp, - ValueNode[] originalArguments, ResolvedJavaMethod target, - ResolvedJavaMethod original) { + private static T getTargetInvokeNode(GraphAdder adder, InvokeFactory factory, IntrinsicMethod intrinsicMethod, + MethodHandleAccessProvider methodHandleAccess, int bci, StampPair returnStamp, ValueNode[] originalArguments, ResolvedJavaMethod target, ResolvedJavaMethod original) { if (target == null || !isConsistentInfo(methodHandleAccess, original, target)) { return null; } @@ -297,7 +295,7 @@ private static InvokeNode getTargetInvokeNode(GraphAdder adder, IntrinsicMethod JavaType parameterType = signature.getParameterType(index, target.getDeclaringClass()); maybeCastArgument(adder, arguments, receiverSkip + index, parameterType); } - InvokeNode invoke = createTargetInvokeNode(assumptions, intrinsicMethod, realTarget, original, bci, returnStamp, arguments); + T invoke = createTargetInvokeNode(factory, assumptions, intrinsicMethod, realTarget, original, bci, returnStamp, arguments); assert invoke != null : "graph has been modified so this must result an invoke"; return invoke; } @@ -308,7 +306,6 @@ private static InvokeNode getTargetInvokeNode(GraphAdder adder, IntrinsicMethod * Inserts a node to cast the argument at index to the given type if the given type is more * concrete than the argument type. * - * @param adder * @param index of the argument to be cast * @param type the type the argument should be cast to */ @@ -356,8 +353,8 @@ private static void maybeCastArgument(GraphAdder adder, ValueNode[] arguments, i * * @return invoke node for the member name target */ - private static InvokeNode createTargetInvokeNode(Assumptions assumptions, IntrinsicMethod intrinsicMethod, ResolvedJavaMethod target, ResolvedJavaMethod original, int bci, StampPair returnStamp, - ValueNode[] arguments) { + private static T createTargetInvokeNode(InvokeFactory factory, Assumptions assumptions, IntrinsicMethod intrinsicMethod, + ResolvedJavaMethod target, ResolvedJavaMethod original, int bci, StampPair returnStamp, ValueNode[] arguments) { InvokeKind targetInvokeKind = target.isStatic() ? InvokeKind.Static : InvokeKind.Special; JavaType targetReturnType = target.getSignature().getReturnType(null); @@ -386,11 +383,11 @@ private static InvokeNode createTargetInvokeNode(Assumptions assumptions, Intrin // we need to use the stamp of the invoker. Note: always using the // invoker's stamp would be wrong because it's a less concrete type // (usually java.lang.Object). + Stamp stamp = targetReturnStamp.getTrustedStamp(); if (returnStamp.getTrustedStamp().getStackKind() == JavaKind.Void) { - return new InvokeNode(callTarget, bci, StampFactory.forVoid()); - } else { - return new InvokeNode(callTarget, bci); + stamp = StampFactory.forVoid(); } + return factory.create(callTarget, bci, stamp); } /** diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleWithExceptionNode.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleWithExceptionNode.java new file mode 100644 index 000000000000..f26d1f3862e0 --- /dev/null +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleWithExceptionNode.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2013, 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 org.graalvm.compiler.replacements.nodes; + +import static org.graalvm.compiler.nodeinfo.NodeCycles.CYCLES_UNKNOWN; +import static org.graalvm.compiler.nodeinfo.NodeSize.SIZE_UNKNOWN; + +import java.lang.invoke.MethodHandle; + +import org.graalvm.compiler.graph.NodeClass; +import org.graalvm.compiler.nodeinfo.NodeInfo; +import org.graalvm.compiler.nodes.InvokeWithExceptionNode; +import org.graalvm.compiler.nodes.ValueNode; +import org.graalvm.compiler.nodes.WithExceptionNode; +import org.graalvm.compiler.nodes.spi.Simplifiable; +import org.graalvm.compiler.nodes.spi.SimplifierTool; +import org.graalvm.compiler.replacements.nodes.MacroNode.MacroParams; +import org.graalvm.compiler.replacements.nodes.MethodHandleNode.GraphAdder; + +import jdk.vm.ci.meta.MethodHandleAccessProvider; +import jdk.vm.ci.meta.MethodHandleAccessProvider.IntrinsicMethod; + +/** + * Node for invocation methods defined on the class {@link MethodHandle}. + */ +@NodeInfo(cycles = CYCLES_UNKNOWN, cyclesRationale = "see MacroNode", size = SIZE_UNKNOWN, sizeRationale = "see MacroNode") +public final class MethodHandleWithExceptionNode extends MacroWithExceptionNode implements Simplifiable { + public static final NodeClass TYPE = NodeClass.create(MethodHandleWithExceptionNode.class); + + protected final IntrinsicMethod intrinsicMethod; + + public MethodHandleWithExceptionNode(IntrinsicMethod intrinsicMethod, MacroParams p) { + super(TYPE, p); + this.intrinsicMethod = intrinsicMethod; + } + + @Override + public void simplify(SimplifierTool tool) { + MethodHandleAccessProvider methodHandleAccess = tool.getConstantReflection().getMethodHandleAccess(); + trySimplify(methodHandleAccess); + } + + public WithExceptionNode trySimplify(MethodHandleAccessProvider methodHandleAccess) { + ValueNode[] argumentsArray = arguments.toArray(new ValueNode[arguments.size()]); + + GraphAdder adder = MethodHandleNode.getGraphAdderBeforeNode(this); + MethodHandleNode.InvokeFactory invokeFactory = (callTarget, bi, stmp) -> { + InvokeWithExceptionNode invoke = new InvokeWithExceptionNode(callTarget, null, bi); + invoke.setStamp(stmp); + return invoke; + }; + InvokeWithExceptionNode invoke = MethodHandleNode.tryResolveTargetInvoke(adder, invokeFactory, methodHandleAccess, intrinsicMethod, targetMethod, bci, returnStamp, argumentsArray); + if (invoke == null) { + return this; + } + assert invoke.graph() == null; + invoke.setNodeSourcePosition(getNodeSourcePosition()); + graph().addOrUniqueWithInputs(invoke); + invoke.setStateAfter(stateAfter()); + graph().replaceWithExceptionSplit(this, invoke); + return invoke; + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java index d0d554c91983..b2728424c163 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java @@ -32,8 +32,6 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import org.graalvm.compiler.debug.GraalError; - import com.oracle.graal.pointsto.api.DefaultUnsafePartition; import com.oracle.graal.pointsto.api.HostVM; import com.oracle.graal.pointsto.api.PointstoOptions; @@ -485,7 +483,8 @@ public int getOffset() { * the hosting HotSpot VM, but it is safer to disallow the operation entirely. The offset * from the hosting VM can be accessed by explicitly calling `wrapped.getOffset()`. */ - throw GraalError.unimplementedOverride(); // ExcludeFromJacocoGeneratedReport + // throw GraalError.unimplementedOverride(); // ExcludeFromJacocoGeneratedReport + return wrapped.getOffset(); // FIXME } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java index a04efd7a0acd..e7614f7b71be 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java @@ -33,6 +33,7 @@ import org.graalvm.compiler.graph.Node; import org.graalvm.compiler.nodes.AbstractEndNode; import org.graalvm.compiler.nodes.AbstractMergeNode; +import org.graalvm.compiler.nodes.CallTargetNode; import org.graalvm.compiler.nodes.ControlSinkNode; import org.graalvm.compiler.nodes.ControlSplitNode; import org.graalvm.compiler.nodes.EncodedGraph; @@ -44,8 +45,11 @@ import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin; import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin; import org.graalvm.compiler.nodes.graphbuilderconf.LoopExplosionPlugin; +import org.graalvm.compiler.nodes.java.MethodCallTargetNode; import org.graalvm.compiler.nodes.util.GraphUtil; import org.graalvm.compiler.replacements.PEGraphDecoder; +import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; +import org.graalvm.compiler.replacements.nodes.ResolvedMethodHandleCallTargetNode; import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; @@ -68,16 +72,13 @@ public class InlineBeforeAnalysisMethodScope extends PEMethodScope { super(targetGraph, caller, callerLoopScope, encodedGraph, method, invokeData, inliningDepth, arguments); if (caller == null) { - /* - * The root method that we are decoding, i.e., inlining into. No policy, because the - * whole method must of course be decoded. - */ + /* The root method that we are decoding, i.e., inlining into. */ policyScope = policy.createRootScope(); if (graph.getDebug().isLogEnabled()) { graph.getDebug().logv(" ".repeat(inliningDepth) + "createRootScope for " + method.format("%H.%n(%p)") + ": " + policyScope); } } else { - policyScope = policy.openCalleeScope(method, (cast(caller)).policyScope); + policyScope = policy.openCalleeScope(cast(caller).policyScope, bb.getMetaAccess(), method, invokeData.intrinsifiedMethodHandle); if (graph.getDebug().isLogEnabled()) { graph.getDebug().logv(" ".repeat(inliningDepth) + "openCalleeScope for " + method.format("%H.%n(%p)") + ": " + policyScope); } @@ -194,6 +195,42 @@ protected LoopScope processNextNode(MethodScope ms, LoopScope loopScope) { return super.processNextNode(methodScope, loopScope); } + @Override + protected LoopScope handleMethodHandle(MethodScope s, LoopScope loopScope, InvokableData invokableData) { + MethodHandleWithExceptionNode node = invokableData.invoke; + Node replacement = node.trySimplify(providers.getConstantReflection().getMethodHandleAccess()); + boolean intrinsifiedMethodHandle = (replacement != node); + InlineBeforeAnalysisMethodScope methodScope = cast(s); + if (!intrinsifiedMethodHandle) { + if (!methodScope.inliningAborted && methodScope.isInlinedMethod()) { + if (!methodScope.policyScope.shouldInterpretMethodHandleInvoke(methodScope.method, node)) { + abortInlining(methodScope); + return loopScope; + } + } + replacement = node.replaceWithInvoke().asNode(); + } + + InvokeWithExceptionNode invoke = (InvokeWithExceptionNode) replacement; + registerNode(loopScope, invokableData.orderId, invoke, true, false); + InvokeData invokeData = new InvokeData(invoke, invokableData.contextType, invokableData.orderId, -1, intrinsifiedMethodHandle, invokableData.stateAfterOrderId, + invokableData.nextOrderId, invokableData.exceptionOrderId, invokableData.exceptionStateOrderId, invokableData.exceptionNextOrderId); + + CallTargetNode callTarget; + if (invoke.callTarget() instanceof ResolvedMethodHandleCallTargetNode t) { + // This special CallTargetNode lowers itself back to the original target (e.g. linkTo*) + // if the invocation hasn't been inlined, which we don't want for Native Image. + callTarget = new MethodCallTargetNode(t.invokeKind(), t.targetMethod(), t.arguments().toArray(ValueNode.EMPTY_ARRAY), t.returnStamp(), t.getTypeProfile()); + } else { + callTarget = (CallTargetNode) invoke.callTarget().copyWithInputs(false); + } + // handleInvoke() expects that CallTargetNode is not eagerly added to the graph + invoke.callTarget().replaceAtUsagesAndDelete(null); + invokeData.callTarget = callTarget; + + return handleInvokeWithCallTarget((PEMethodScope) s, loopScope, invokeData); + } + @Override protected void finishInlining(MethodScope is) { InlineBeforeAnalysisMethodScope inlineScope = cast(is); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java index 67b632ab65fe..98795e1f8086 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java @@ -30,6 +30,7 @@ import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext; import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin.InlineInfo; import org.graalvm.compiler.nodes.graphbuilderconf.NodePlugin; +import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.util.AnalysisError; @@ -77,6 +78,14 @@ protected AbstractPolicyScope(int inliningDepth) { * decision on the current list of usages. The list of usages is often but not always empty. */ public abstract boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, Node node); + + /** + * The given {@link MethodHandleWithExceptionNode} could not be intrinsified. If this method + * returns {@code true}, the node is instead replaced with a call into the method handle + * interpreter and inlining continues. If this method returns {@code false}, inlining is + * aborted. + */ + protected abstract boolean shouldInterpretMethodHandleInvoke(ResolvedJavaMethod method, MethodHandleWithExceptionNode node); } protected final NodePlugin[] nodePlugins; @@ -97,7 +106,7 @@ protected InlineBeforeAnalysisPolicy(NodePlugin[] nodePlugins) { protected abstract AbstractPolicyScope createRootScope(); - protected abstract AbstractPolicyScope openCalleeScope(ResolvedJavaMethod method, AbstractPolicyScope outer); + protected abstract AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle); public static final InlineBeforeAnalysisPolicy NO_INLINING = new InlineBeforeAnalysisPolicy(new NodePlugin[0]) { @@ -137,7 +146,7 @@ protected AbstractPolicyScope createRootScope() { } @Override - protected AbstractPolicyScope openCalleeScope(ResolvedJavaMethod method, AbstractPolicyScope outer) { + protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle) { throw AnalysisError.shouldNotReachHere("NO_INLINING policy should not try to inline"); } }; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SharedConstantReflectionProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SharedConstantReflectionProvider.java index 49b6b457aa8d..699dfd67bec2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SharedConstantReflectionProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/meta/SharedConstantReflectionProvider.java @@ -129,7 +129,7 @@ public JavaConstant forString(String value) { } @Override - public final MethodHandleAccessProvider getMethodHandleAccess() { + public MethodHandleAccessProvider getMethodHandleAccess() { throw shouldNotReachHereAtRuntime(); // ExcludeFromJacocoGeneratedReport } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java index 6a1ebb1e9ff2..54c8944990c5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java @@ -51,8 +51,8 @@ /** * This file contains most of the code necessary for supporting VarHandle in native images. The - * actual intrinsification of VarHandle accessors is in hosted-only code in the - * IntrinsifyMethodHandlesInvocationPlugin. + * actual intrinsification of VarHandle accessors happens in hosted-only code during inlining before + * analysis. * * The VarHandle implementation in the JDK uses some invokedynamic and method handles, but also a * lot of explicit Java code (a lot of it automatically generated): The main entry point from the @@ -60,12 +60,13 @@ * prototypes for the various access modes. However, we do not need to do anything special for the * VarHandle class: when we parse bytecode, all the bootstrapping has already happened on the Java * HotSpot VM, and the bytecode parser already sees calls to guard methods defined in - * VarHandleGuards. Method of that class are the intrinsification root for the - * IntrinsifyMethodHandlesInvocationPlugin. The intrinsification removes all the method handle - * invocation logic and reduces the logic to a single call to the actual access logic. This logic is - * in various automatically generated accessor classes named + * VarHandleGuards. Methods of that class are method handle intrinsification roots for inlining + * before analysis. The intrinsification removes all the method handle invocation logic and reduces + * the logic to a single call to the actual access logic. This logic is in various automatically + * generated accessor classes named * "VarHandle{Booleans|Bytes|Chars|Doubles|Floats|Ints|Longs|Shorts|Objects}.{Array|FieldInstanceReadOnly|FieldInstanceReadWrite|FieldStaticReadOnly|FieldStaticReadWrite}". - * The intrinsification must not inline these methods, because they contain complicated logic. + * The intrinsification might be able to inline these methods and even transform unsafe accesses by + * offset to field accesses, but we cannot rely on it always being able to do in every case. * * The accessor classes for field access (both instance and static field access) store the offset of * the field that is used for Unsafe memory access. We need to 1) properly register these fields as diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java index ad376ea9d251..78bcecbadfe4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java @@ -38,6 +38,7 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; import com.oracle.svm.core.invoke.MethodHandleUtils; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.reflect.SubstrateMethodAccessor; @@ -232,6 +233,16 @@ static Object invokeInternal(Target_java_lang_invoke_MemberName memberName, Meth } } +@TargetClass(className = "java.lang.invoke.DirectMethodHandle") +final class Target_java_lang_invoke_DirectMethodHandle { + @Alias Target_java_lang_invoke_MemberName member; + + @Substitute + void ensureInitialized() { + EnsureClassInitializedNode.ensureClassInitialized(member.getDeclaringClass()); + } +} + @TargetClass(className = "java.lang.invoke.MethodHandleImpl") final class Target_java_lang_invoke_MethodHandleImpl { } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceRuntimeCompilationFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceRuntimeCompilationFeature.java index 1b5b217aea14..f4e3a06555b8 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceRuntimeCompilationFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceRuntimeCompilationFeature.java @@ -1018,10 +1018,10 @@ protected AbstractPolicyScope createRootScope() { } @Override - protected AbstractPolicyScope openCalleeScope(ResolvedJavaMethod method, AbstractPolicyScope outer) { + protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle) { if (outer instanceof AccumulativeInlineScope accOuter) { // once the accumulative policy is activated, then we cannot return to the trivial policy - return InlineBeforeAnalysisPolicyUtils.createAccumulativeInlineScope(accOuter, inliningUtils); + return inliningUtils.createAccumulativeInlineScope(accOuter, metaAccess, method, intrinsifiedMethodHandle); } assert outer == null || outer instanceof AlwaysInlineScope : "unexpected outer scope: " + outer; @@ -1033,7 +1033,7 @@ protected AbstractPolicyScope openCalleeScope(ResolvedJavaMethod method, Abstrac return new AlwaysInlineScope(inliningDepth); } else { // start with a new accumulative inline scope - return InlineBeforeAnalysisPolicyUtils.createAccumulativeInlineScope(null, inliningUtils); + return inliningUtils.createAccumulativeInlineScope(null, metaAccess, method, intrinsifiedMethodHandle); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 2d764621f02c..b9bcdfb6fce5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -107,6 +107,7 @@ import org.graalvm.compiler.phases.tiers.Suites; import org.graalvm.compiler.phases.util.Providers; import org.graalvm.compiler.printer.GraalDebugHandlersFactory; +import org.graalvm.compiler.replacements.MethodHandleWithExceptionPlugin; import org.graalvm.compiler.replacements.NodeIntrinsificationProvider; import org.graalvm.compiler.replacements.TargetGraphBuilderPlugins; import org.graalvm.compiler.word.WordOperationPlugin; @@ -1303,7 +1304,13 @@ public static void registerGraphBuilderPlugins(FeatureHandler featureHandler, Ru SubstrateReplacements replacements = (SubstrateReplacements) providers.getReplacements(); plugins.appendInlineInvokePlugin(replacements); - plugins.appendNodePlugin(new IntrinsifyMethodHandlesInvocationPlugin(reason, providers, aUniverse, hUniverse)); + if (SubstrateOptions.parseOnce()) { + if (reason.duringAnalysis()) { + plugins.appendNodePlugin(new MethodHandleWithExceptionPlugin(providers.getConstantReflection().getMethodHandleAccess(), false)); + } + } else { + plugins.appendNodePlugin(new IntrinsifyMethodHandlesInvocationPlugin(reason, providers, aUniverse, hUniverse)); + } plugins.appendNodePlugin(new DeletedFieldsPlugin()); plugins.appendNodePlugin(new InjectedAccessorsPlugin()); plugins.appendNodePlugin(new EarlyConstantFoldLoadFieldPlugin(providers.getMetaAccess())); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java index 8864e0e2e89c..ea35bb186fbd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java @@ -57,6 +57,7 @@ import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.MemoryAccessProvider; +import jdk.vm.ci.meta.MethodHandleAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaType; @@ -65,11 +66,13 @@ public class AnalysisConstantReflectionProvider extends SharedConstantReflection private final AnalysisUniverse universe; private final UniverseMetaAccess metaAccess; private final ClassInitializationSupport classInitializationSupport; + private final AnalysisMethodHandleAccessProvider methodHandleAccess; public AnalysisConstantReflectionProvider(AnalysisUniverse universe, UniverseMetaAccess metaAccess, ClassInitializationSupport classInitializationSupport) { this.universe = universe; this.metaAccess = metaAccess; this.classInitializationSupport = classInitializationSupport; + this.methodHandleAccess = new AnalysisMethodHandleAccessProvider(universe); } @Override @@ -116,6 +119,11 @@ public JavaConstant unboxPrimitive(JavaConstant source) { return JavaConstant.forBoxedPrimitive(SubstrateObjectConstant.asObject(source)); } + @Override + public MethodHandleAccessProvider getMethodHandleAccess() { + return methodHandleAccess; + } + @Override public Integer readArrayLength(JavaConstant array) { if (array.getJavaKind() != JavaKind.Object || array.isNull()) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisMethodHandleAccessProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisMethodHandleAccessProvider.java new file mode 100644 index 000000000000..cdf218947270 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisMethodHandleAccessProvider.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023, 2023, 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.hosted.ameta; + +import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.graal.pointsto.infrastructure.GraphProvider; +import com.oracle.graal.pointsto.infrastructure.OriginalMethodProvider; +import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.graal.pointsto.util.GraalAccess; + +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.MethodHandleAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaMethod; + +@Platforms(Platform.HOSTED_ONLY.class) +final class AnalysisMethodHandleAccessProvider implements MethodHandleAccessProvider { + private final AnalysisUniverse analysisUniverse; + private final MethodHandleAccessProvider originalMethodHandleAccess; + private final SnippetReflectionProvider originalSnippetReflection; + + AnalysisMethodHandleAccessProvider(AnalysisUniverse analysisUniverse) { + assert analysisUniverse != null; + this.analysisUniverse = analysisUniverse; + this.originalMethodHandleAccess = GraalAccess.getOriginalProviders().getConstantReflection().getMethodHandleAccess(); + this.originalSnippetReflection = GraalAccess.getOriginalSnippetReflection(); + } + + @Override + public IntrinsicMethod lookupMethodHandleIntrinsic(ResolvedJavaMethod method) { + ResolvedJavaMethod unwrapped = ((AnalysisMethod) method).getWrapped(); + unwrapped = analysisUniverse.resolveSubstitution(unwrapped); + assert !(unwrapped instanceof WrappedJavaMethod || unwrapped instanceof OriginalMethodProvider); + if (unwrapped instanceof GraphProvider) { + return null; + } + return originalMethodHandleAccess.lookupMethodHandleIntrinsic(unwrapped); + } + + @Override + public ResolvedJavaMethod resolveInvokeBasicTarget(JavaConstant methodHandle, boolean forceBytecodeGeneration) { + JavaConstant originalMethodHandle = toOriginalConstant(methodHandle); + ResolvedJavaMethod originalTarget = originalMethodHandleAccess.resolveInvokeBasicTarget(originalMethodHandle, forceBytecodeGeneration); + return analysisUniverse.lookup(originalTarget); + } + + @Override + public ResolvedJavaMethod resolveLinkToTarget(JavaConstant memberName) { + JavaConstant originalMemberName = toOriginalConstant(memberName); + ResolvedJavaMethod method = originalMethodHandleAccess.resolveLinkToTarget(originalMemberName); + return analysisUniverse.lookup(method); + } + + private JavaConstant toOriginalConstant(JavaConstant constant) { + Object obj = analysisUniverse.getSnippetReflection().asObject(Object.class, constant); + return originalSnippetReflection.forObject(obj); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/InliningUtilities.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/InliningUtilities.java index 06b642b73230..44c4f0245e60 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/InliningUtilities.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/InliningUtilities.java @@ -33,6 +33,7 @@ import org.graalvm.compiler.nodes.extended.ValueAnchorNode; import org.graalvm.compiler.nodes.java.MethodCallTargetNode; import org.graalvm.compiler.nodes.spi.ValueProxy; +import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; import com.oracle.svm.core.SubstrateOptions; @@ -45,7 +46,7 @@ public static boolean isTrivialMethod(StructuredGraph graph) { if (n instanceof StartNode || n instanceof ParameterNode || n instanceof FullInfopointNode || n instanceof ValueProxy || n instanceof ValueAnchorNode || n instanceof FrameState) { continue; } - if (n instanceof MethodCallTargetNode) { + if (n instanceof MethodCallTargetNode || n instanceof MethodHandleWithExceptionNode) { numInvokes++; } else { numOthers++; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingFeature.java index 3057ec73826e..05104bf98276 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/fieldfolding/StaticFinalFieldFoldingFeature.java @@ -53,7 +53,6 @@ import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.meta.HostedField; -import com.oracle.svm.hosted.phases.IntrinsifyMethodHandlesInvocationPlugin; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.ResolvedJavaField; @@ -64,8 +63,8 @@ * folded by the regular constant folding mechanism. But if a class is initialized at run time, the * class initializer of that class is analyzed like any other method, i.e., the static analysis sees * a static final field as written and does not perform constant folding. Without constant folding - * during parsing already, other graph builder plugins like - * {@link IntrinsifyMethodHandlesInvocationPlugin} do not work on such fields. + * during parsing already, other simplifications and intrinsifications do not work on such fields, + * such as those involving method handles. * * This feature performs constant folding for a limited but important class of static final fields: * the class initializer contains a single field store and the stored value is a constant. That diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java index 5c07008a77e2..5c6dfa3d7bdc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java @@ -106,8 +106,9 @@ protected AbstractPolicyScope createRootScope() { } @Override - protected AbstractPolicyScope openCalleeScope(ResolvedJavaMethod method, AbstractPolicyScope outer) { - return InlineBeforeAnalysisPolicyUtils.createAccumulativeInlineScope((InlineBeforeAnalysisPolicyUtils.AccumulativeInlineScope) outer, inliningUtils); + protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle) { + return inliningUtils.createAccumulativeInlineScope((InlineBeforeAnalysisPolicyUtils.AccumulativeInlineScope) outer, + metaAccess, method, intrinsifiedMethodHandle); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java index 915ff63f2a76..d4ba3aaff8ae 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java @@ -24,6 +24,11 @@ */ package com.oracle.svm.hosted.phases; +import java.lang.invoke.MethodHandle; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.graph.Node; import org.graalvm.compiler.nodes.AbstractBeginNode; @@ -49,19 +54,24 @@ import org.graalvm.compiler.nodes.virtual.VirtualArrayNode; import org.graalvm.compiler.nodes.virtual.VirtualObjectNode; import org.graalvm.compiler.options.Option; +import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; import org.graalvm.nativeimage.AnnotationAccess; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.phases.InlineBeforeAnalysisPolicy; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.heap.RestrictHeapAccess; +import com.oracle.svm.core.jdk.VarHandleFeature; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ReachabilityRegistrationNode; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; public class InlineBeforeAnalysisPolicyUtils { public static class Options { @@ -75,6 +85,14 @@ public static class Options { public static final HostedOptionKey InlineBeforeAnalysisAllowedDepth = new HostedOptionKey<>(20); } + private static final Map> IGNORED_METHOD_HANDLE_METHODS = Map.of( + "java.lang.invoke.MethodHandle", Set.of("bindTo"), + "java.lang.invoke.MethodHandles", Set.of("dropArguments", "filterReturnValue", "foldArguments", "insertArguments"), + "java.lang.invoke.Invokers", Set.of("spreadInvoker")); + + private AnalysisType methodHandleType; + private AnalysisType varHandleGuardsType; + public static boolean inliningAllowed(SVMHost hostVM, GraphBuilderContext b, ResolvedJavaMethod method) { AnalysisMethod caller = (AnalysisMethod) b.getMethod(); AnalysisMethod callee = (AnalysisMethod) method; @@ -145,19 +163,44 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met return true; } + @Override + protected boolean shouldInterpretMethodHandleInvoke(ResolvedJavaMethod method, MethodHandleWithExceptionNode node) { + // always inlining + return true; + } + @Override public String toString() { return "AlwaysInlineScope"; } } - static class AccumulativeCounters { - final int maxNodes = Options.InlineBeforeAnalysisAllowedNodes.getValue(); - final int maxInvokes = Options.InlineBeforeAnalysisAllowedInvokes.getValue(); - final int maxInliningDepth = Options.InlineBeforeAnalysisAllowedDepth.getValue(); + static final class AccumulativeCounters { + static AccumulativeCounters createRoot() { + return new AccumulativeCounters(Options.InlineBeforeAnalysisAllowedNodes.getValue(), + Options.InlineBeforeAnalysisAllowedInvokes.getValue(), + Options.InlineBeforeAnalysisAllowedDepth.getValue(), + false); + } + + static AccumulativeCounters createForMethodHandleIntrinsification(int startDepth) { + return new AccumulativeCounters(100, 20, startDepth + 20, true); + } + + int maxNodes; + int maxInvokes; + final int maxInliningDepth; + final boolean inMethodHandleIntrinsification; int numNodes = 0; int numInvokes = 0; + + private AccumulativeCounters(int maxNodes, int maxInvokes, int maxInliningDepth, boolean inMethodHandleIntrinsification) { + this.maxNodes = maxNodes; + this.maxInvokes = maxInvokes; + this.maxInliningDepth = maxInliningDepth; + this.inMethodHandleIntrinsification = inMethodHandleIntrinsification; + } } /** @@ -165,7 +208,7 @@ static class AccumulativeCounters { * has exceeded a specified count, or an illegal node is inlined, then the process will be * aborted. */ - public static AccumulativeInlineScope createAccumulativeInlineScope(AccumulativeInlineScope outer, InlineBeforeAnalysisPolicyUtils inliningUtils) { + public AccumulativeInlineScope createAccumulativeInlineScope(AccumulativeInlineScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle) { AccumulativeCounters accumulativeCounters; int depth; if (outer == null) { @@ -173,19 +216,60 @@ public static AccumulativeInlineScope createAccumulativeInlineScope(Accumulative * The first level of method inlining, i.e., the top scope from the inlining policy * point of view. */ - accumulativeCounters = new AccumulativeCounters(); depth = 1; + accumulativeCounters = AccumulativeCounters.createRoot(); + + } else if (!outer.accumulativeCounters.inMethodHandleIntrinsification && (intrinsifiedMethodHandle || isMethodHandleIntrinsificationRoot(metaAccess, method))) { + /* + * Method handle intrinsification root: create counters with relaxed limits and permit + * more types of nodes, but not recursively, i.e., not if we are already in a method + * handle intrinsification context. + */ + depth = outer.inliningDepth + 1; + accumulativeCounters = AccumulativeCounters.createForMethodHandleIntrinsification(depth); + } else { - /* Nested inlining. */ - accumulativeCounters = outer.accumulativeCounters; + /* Nested inlining (potentially during method handle intrinsification). */ depth = outer.inliningDepth + 1; + accumulativeCounters = outer.accumulativeCounters; + } + return new AccumulativeInlineScope(accumulativeCounters, depth); + } + + private boolean isMethodHandleIntrinsificationRoot(AnalysisMetaAccess metaAccess, ResolvedJavaMethod method) { + return (isVarHandleMethod(metaAccess, method) || hasMethodHandleParameter(metaAccess, method)) && !isIgnoredMethodHandleMethod(method); + } + + private boolean hasMethodHandleParameter(AnalysisMetaAccess metaAccess, ResolvedJavaMethod method) { + if (methodHandleType == null) { + methodHandleType = metaAccess.lookupJavaType(MethodHandle.class); } - return new AccumulativeInlineScope(accumulativeCounters, depth, inliningUtils); + return Arrays.stream(method.toParameterTypes()).anyMatch(type -> methodHandleType.isAssignableFrom((ResolvedJavaType) type)); } - public static class AccumulativeInlineScope extends InlineBeforeAnalysisPolicy.AbstractPolicyScope { + /** + * Checks if the method is the intrinsification root for a VarHandle. In the current VarHandle + * implementation, all guards are in the automatically generated class VarHandleGuards. All + * methods do have a VarHandle argument, and we expect it to be a compile-time constant. + *

+ * See the documentation in {@link VarHandleFeature} for more information on the overall + * VarHandle support. + */ + private boolean isVarHandleMethod(AnalysisMetaAccess metaAccess, ResolvedJavaMethod method) { + if (varHandleGuardsType == null) { + varHandleGuardsType = metaAccess.lookupJavaType(ReflectionUtil.lookupClass(false, "java.lang.invoke.VarHandleGuards")); + } + return method.getDeclaringClass().equals(varHandleGuardsType); + } + + private static boolean isIgnoredMethodHandleMethod(ResolvedJavaMethod method) { + String className = method.getDeclaringClass().toJavaName(true); + Set ignoredMethods = IGNORED_METHOD_HANDLE_METHODS.get(className); + return ignoredMethods != null && ignoredMethods.contains(method.getName()); + } + + public final class AccumulativeInlineScope extends InlineBeforeAnalysisPolicy.AbstractPolicyScope { final AccumulativeCounters accumulativeCounters; - final InlineBeforeAnalysisPolicyUtils inliningUtils; /** * The number of nodes and invokes which have been inlined from this method (and also @@ -195,10 +279,13 @@ public static class AccumulativeInlineScope extends InlineBeforeAnalysisPolicy.A int numNodes = 0; int numInvokes = 0; - AccumulativeInlineScope(AccumulativeCounters accumulativeCounters, int inliningDepth, InlineBeforeAnalysisPolicyUtils inliningUtils) { + Boolean lenientForMethodHandleIntrinsic = null; // lazily initialized + boolean calleeMethodHandleIntrinsicFailed = false; + boolean methodHandleIntrinsicFailed = false; + + AccumulativeInlineScope(AccumulativeCounters accumulativeCounters, int inliningDepth) { super(inliningDepth); this.accumulativeCounters = accumulativeCounters; - this.inliningUtils = inliningUtils; } @Override @@ -210,7 +297,16 @@ public boolean allowAbort() { @Override public void commitCalleeScope(InlineBeforeAnalysisPolicy.AbstractPolicyScope callee) { AccumulativeInlineScope calleeScope = (AccumulativeInlineScope) callee; - assert accumulativeCounters == calleeScope.accumulativeCounters; + if (accumulativeCounters != calleeScope.accumulativeCounters) { + assert !accumulativeCounters.inMethodHandleIntrinsification && calleeScope.accumulativeCounters.inMethodHandleIntrinsification; + + // Expand limits to hold the method handle intrinsification, but not more. + accumulativeCounters.maxNodes += calleeScope.numNodes; + accumulativeCounters.maxInvokes += calleeScope.numInvokes; + + accumulativeCounters.numNodes += calleeScope.numNodes; + accumulativeCounters.numInvokes += calleeScope.numInvokes; + } numNodes += calleeScope.numNodes; numInvokes += calleeScope.numInvokes; } @@ -218,14 +314,27 @@ public void commitCalleeScope(InlineBeforeAnalysisPolicy.AbstractPolicyScope cal @Override public void abortCalleeScope(InlineBeforeAnalysisPolicy.AbstractPolicyScope callee) { AccumulativeInlineScope calleeScope = (AccumulativeInlineScope) callee; - assert accumulativeCounters == calleeScope.accumulativeCounters; - accumulativeCounters.numNodes -= calleeScope.numNodes; - accumulativeCounters.numInvokes -= calleeScope.numInvokes; + if (accumulativeCounters == calleeScope.accumulativeCounters) { + accumulativeCounters.numNodes -= calleeScope.numNodes; + accumulativeCounters.numInvokes -= calleeScope.numInvokes; + } else { + assert !accumulativeCounters.inMethodHandleIntrinsification && calleeScope.accumulativeCounters.inMethodHandleIntrinsification; + } + calleeMethodHandleIntrinsicFailed = calleeScope.methodHandleIntrinsicFailed; } @Override public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, Node node) { - if (inliningUtils.alwaysInlineInvoke(metaAccess, method)) { + if (node instanceof StartNode || node instanceof ParameterNode || node instanceof ReturnNode || node instanceof UnwindNode || + node instanceof CallTargetNode || node instanceof MethodHandleWithExceptionNode) { + /* + * Infrastructure nodes and call targets are not intended to be visible to the + * policy. Method handle calls must have been transformed to an invoke already. + */ + throw VMError.shouldNotReachHere("Node must not be visible to policy: " + node.getClass().getTypeName()); + } + + if (alwaysInlineInvoke(metaAccess, method)) { return true; } @@ -234,10 +343,12 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met return false; } - if (node instanceof StartNode || node instanceof ParameterNode || node instanceof ReturnNode || node instanceof UnwindNode) { - /* Infrastructure nodes that are not even visible to the policy. */ - throw VMError.shouldNotReachHere("Node must not be visible to policy: " + node.getClass().getTypeName()); + if (lenientForMethodHandleIntrinsic == Boolean.TRUE && calleeMethodHandleIntrinsicFailed) { + assert !methodHandleIntrinsicFailed : "must have failed earlier"; + methodHandleIntrinsicFailed = true; // propagates to caller + return false; } + if (node instanceof FullInfopointNode || node instanceof ValueProxy || node instanceof ValueAnchorNode || node instanceof FrameState || node instanceof AbstractBeginNode || node instanceof AbstractEndNode) { /* @@ -262,10 +373,11 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met return true; } + boolean allow = true; + if (node instanceof AbstractNewObjectNode) { /* - * We never allow to inline any kind of allocations, because the machine code size - * is large. + * We do not inline any kind of allocations because the machine code size is large. * * With one important exception: we allow (and do not even count) arrays allocated * with length 0. Such allocations occur when a method has a Java vararg parameter @@ -282,7 +394,8 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met return true; } } - return false; + allow = false; + } else if (node instanceof VirtualObjectNode) { /* * Same as the explicit allocation nodes above, but this time for the virtualized @@ -294,18 +407,13 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met return true; } } - return false; + allow = false; + } else if (node instanceof CommitAllocationNode || node instanceof AllocatedObjectNode) { - /* - * Ignore nodes created by escape analysis in addition to the VirtualInstanceNode. - */ + /* Ignore nodes created by escape analysis in addition to the VirtualObjectNode. */ return true; } - if (node instanceof CallTargetNode) { - throw VMError.shouldNotReachHere("Node must not be visible to policy: " + node.getClass().getTypeName()); - } - if (node instanceof Invoke) { if (accumulativeCounters.numInvokes >= accumulativeCounters.maxInvokes) { return false; @@ -320,7 +428,25 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met numNodes++; accumulativeCounters.numNodes++; - return true; + if (!allow && accumulativeCounters.inMethodHandleIntrinsification) { + if (lenientForMethodHandleIntrinsic == null) { + lenientForMethodHandleIntrinsic = inlineForMethodHandleIntrinsification(method); + } + if (lenientForMethodHandleIntrinsic) { + if (calleeMethodHandleIntrinsicFailed) { + methodHandleIntrinsicFailed = true; // propagates to caller + return false; + } + allow = true; + } + } + return allow; + } + + @Override + protected boolean shouldInterpretMethodHandleInvoke(ResolvedJavaMethod method, MethodHandleWithExceptionNode node) { + methodHandleIntrinsicFailed = true; + return false; } @Override @@ -328,4 +454,31 @@ public String toString() { return "AccumulativeInlineScope: " + numNodes + "/" + numInvokes + " (" + accumulativeCounters.numNodes + "/" + accumulativeCounters.numInvokes + ")"; } } + + private static boolean inlineForMethodHandleIntrinsification(ResolvedJavaMethod method) { + String className = method.getDeclaringClass().toJavaName(true); + if (className.startsWith("java.lang.invoke.VarHandle") && (!className.equals("java.lang.invoke.VarHandle") || method.getName().equals("getMethodHandleUncached"))) { + /* + * Do not inline implementation methods of various VarHandle implementation classes. + * They are too complex and cannot be reduced to a single invoke or field access. There + * is also no need to inline them, because they are not related to any MethodHandle + * mechanism. + * + * Methods defined in VarHandle itself are fine and not covered by this rule, apart from + * well-known methods that are never useful to be inlined. If these methods are reached, + * intrinsification will not be possible in any case. + */ + return false; + } else if (className.startsWith("java.lang.invoke") && !className.contains("InvokerBytecodeGenerator")) { + /* + * Inline all helper methods used by method handles. We do not know exactly which ones + * they are, but they are all from the same package. + */ + return true; + } else if (className.equals("sun.invoke.util.ValueConversions")) { + /* Inline trivial helper methods for value conversion. */ + return true; + } + return false; + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/IntrinsifyMethodHandlesInvocationPlugin.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/IntrinsifyMethodHandlesInvocationPlugin.java index 51c1eae018c4..bd869a6665bb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/IntrinsifyMethodHandlesInvocationPlugin.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/IntrinsifyMethodHandlesInvocationPlugin.java @@ -141,6 +141,11 @@ import jdk.vm.ci.meta.ResolvedJavaType; /** + * Legacy code which will be replaced by the more general method handle intrinsification and + * inlining in {@link com.oracle.graal.pointsto.phases.InlineBeforeAnalysisGraphDecoder} once + * {@link com.oracle.svm.core.SubstrateOptions#parseOnce()} is always on, including when JIT + * compilation is enabled. + * * Support for method handles that can be reduced to a plain invocation. This is enough to support * the method handles used for Java 8 Lambda expressions. Support for arbitrary method handles is * not possible in the Substrate VM, for the same reasons that we cannot support arbitrary diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/UnsafeAutomaticSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/UnsafeAutomaticSubstitutionProcessor.java index fc6b2ed96dbc..8f6833e0863d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/UnsafeAutomaticSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/UnsafeAutomaticSubstitutionProcessor.java @@ -184,7 +184,7 @@ public void init(ImageClassLoader loader, MetaAccessProvider originalMetaAccess) /* * Various factory methods in VarHandle query the array base/index or field offsets. * There is no need to analyze these calls because VarHandle accesses are intrinsified - * to simple array and field access nodes in IntrinsifyMethodHandlesInvocationPlugin. + * to simple array and field access nodes during inlining before analysis. */ for (Method method : loader.findClassOrFail("java.lang.invoke.VarHandles").getDeclaredMethods()) { neverInlineSet.add(originalMetaAccess.lookupJavaMethod(method)); From fa684841d4630d6681066f83701e8e29e29008fc Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Mon, 12 Jun 2023 22:34:29 +0200 Subject: [PATCH 05/33] Transform unsafe field accesses during inlining before analysis. --- .../compiler/nodes/extended/RawLoadNode.java | 8 ++- .../compiler/nodes/extended/RawStoreNode.java | 8 ++- .../nodes/extended/UnsafeAccessNode.java | 8 +-- .../com/oracle/graal/pointsto/api/HostVM.java | 11 ++++ .../graal/pointsto/meta/AnalysisField.java | 5 +- .../InlineBeforeAnalysisGraphDecoder.java | 59 +++++++++++++++++-- .../src/com/oracle/svm/hosted/SVMHost.java | 12 +++- 7 files changed, 92 insertions(+), 19 deletions(-) diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/RawLoadNode.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/RawLoadNode.java index 7de3049eff8d..4d12f518168c 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/RawLoadNode.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/RawLoadNode.java @@ -35,6 +35,7 @@ import org.graalvm.compiler.graph.Node; import org.graalvm.compiler.graph.NodeClass; import org.graalvm.compiler.nodeinfo.NodeInfo; +import org.graalvm.compiler.nodes.GraphState; import org.graalvm.compiler.nodes.NodeView; import org.graalvm.compiler.nodes.ValueNode; import org.graalvm.compiler.nodes.calc.ReinterpretNode; @@ -52,7 +53,6 @@ import org.graalvm.compiler.nodes.virtual.VirtualObjectNode; import org.graalvm.word.LocationIdentity; -import jdk.vm.ci.meta.Assumptions; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaField; @@ -194,8 +194,10 @@ public Node canonical(CanonicalizerTool tool) { } @Override - protected ValueNode cloneAsFieldAccess(Assumptions assumptions, ResolvedJavaField field, MemoryOrderMode memOrder) { - return LoadFieldNode.create(assumptions, field.isStatic() ? null : object(), field, memOrder); + public ValueNode cloneAsFieldAccess(ResolvedJavaField field) { + assert field.getJavaKind() == accessKind() && !field.isInternal(); + assert graph().isBeforeStage(GraphState.StageFlag.FLOATING_READS) : "cannot add more precise memory location after floating read phase"; + return LoadFieldNode.create(graph().getAssumptions(), field.isStatic() ? null : object(), field, getMemoryOrder()); } @Override diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/RawStoreNode.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/RawStoreNode.java index e0a83c3a689a..db364dbd20c3 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/RawStoreNode.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/RawStoreNode.java @@ -33,6 +33,7 @@ import org.graalvm.compiler.graph.NodeClass; import org.graalvm.compiler.nodeinfo.NodeInfo; import org.graalvm.compiler.nodes.FrameState; +import org.graalvm.compiler.nodes.GraphState; import org.graalvm.compiler.nodes.StateSplit; import org.graalvm.compiler.nodes.ValueNode; import org.graalvm.compiler.nodes.java.StoreFieldNode; @@ -43,7 +44,6 @@ import org.graalvm.compiler.nodes.virtual.VirtualObjectNode; import org.graalvm.word.LocationIdentity; -import jdk.vm.ci.meta.Assumptions; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaField; @@ -148,8 +148,10 @@ public void virtualize(VirtualizerTool tool) { } @Override - protected ValueNode cloneAsFieldAccess(Assumptions assumptions, ResolvedJavaField field, MemoryOrderMode memOrder) { - return new StoreFieldNode(field.isStatic() ? null : object(), field, value(), stateAfter(), memOrder); + public ValueNode cloneAsFieldAccess(ResolvedJavaField field) { + assert field.getJavaKind() == accessKind() && !field.isInternal(); + assert graph().isBeforeStage(GraphState.StageFlag.FLOATING_READS) : "cannot add more precise memory location after floating read phase"; + return new StoreFieldNode(field.isStatic() ? null : object(), field, value(), stateAfter(), getMemoryOrder()); } @Override diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/UnsafeAccessNode.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/UnsafeAccessNode.java index 720209d370fa..096eb50ad768 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/UnsafeAccessNode.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/extended/UnsafeAccessNode.java @@ -45,7 +45,6 @@ import org.graalvm.compiler.nodes.type.StampTool; import org.graalvm.word.LocationIdentity; -import jdk.vm.ci.meta.Assumptions; import jdk.vm.ci.meta.ConstantReflectionProvider; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; @@ -129,8 +128,7 @@ public Node canonical(CanonicalizerTool tool) { // this is never a valid access of an arbitrary address. if ((field != null && field.getJavaKind() == this.accessKind() && !field.isInternal() /* Ensure this is a true java field. */)) { - assert graph().isBeforeStage(StageFlag.FLOATING_READS) : "cannot add more precise memory location after floating read phase"; - return cloneAsFieldAccess(graph().getAssumptions(), field, getMemoryOrder()); + return cloneAsFieldAccess(field); } } } @@ -152,7 +150,7 @@ public Node canonical(CanonicalizerTool tool) { return this; } - protected abstract ValueNode cloneAsFieldAccess(Assumptions assumptions, ResolvedJavaField field, MemoryOrderMode memOrder); + public abstract ValueNode cloneAsFieldAccess(ResolvedJavaField field); protected abstract ValueNode cloneAsArrayAccess(ValueNode location, LocationIdentity identity, MemoryOrderMode memOrder); @@ -179,7 +177,7 @@ private ResolvedJavaField getStaticFieldUnsafeAccess(ConstantReflectionProvider return findStaticFieldWithOffset(staticReceiverType, offsetConstant.asLong(), accessKind); } - private static ResolvedJavaField findStaticFieldWithOffset(ResolvedJavaType type, long offset, JavaKind expectedEntryKind) { + public static ResolvedJavaField findStaticFieldWithOffset(ResolvedJavaType type, long offset, JavaKind expectedEntryKind) { try { ResolvedJavaField[] declaredFields = type.getStaticFields(); return findFieldWithOffset(offset, expectedEntryKind, declaredFields); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java index 1def9076770e..73dfcd7394a7 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java @@ -391,4 +391,15 @@ public Function getStrengthenGraphsToTargetFunct public FieldValueComputer createFieldValueComputer(@SuppressWarnings("unused") AnalysisField field) { return null; } + + /** + * Returns the "naked" original type in the hosting VM that corresponds to the passed + * {@link AnalysisType} without any injected or changed members or attributes. + * + * In most cases, {@link AnalysisType#getWrapped} or {@link AnalysisType#getWrappedWithResolve} + * are better alternatives to this method. + */ + public ResolvedJavaType getOriginalHostType(AnalysisType type) { + return type.getWrapped(); + } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java index b2728424c163..d0d554c91983 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java @@ -32,6 +32,8 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import org.graalvm.compiler.debug.GraalError; + import com.oracle.graal.pointsto.api.DefaultUnsafePartition; import com.oracle.graal.pointsto.api.HostVM; import com.oracle.graal.pointsto.api.PointstoOptions; @@ -483,8 +485,7 @@ public int getOffset() { * the hosting HotSpot VM, but it is safer to disallow the operation entirely. The offset * from the hosting VM can be accessed by explicitly calling `wrapped.getOffset()`. */ - // throw GraalError.unimplementedOverride(); // ExcludeFromJacocoGeneratedReport - return wrapped.getOffset(); // FIXME + throw GraalError.unimplementedOverride(); // ExcludeFromJacocoGeneratedReport } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java index e7614f7b71be..2b3c64145fea 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java @@ -42,10 +42,12 @@ import org.graalvm.compiler.nodes.InvokeWithExceptionNode; import org.graalvm.compiler.nodes.StructuredGraph; import org.graalvm.compiler.nodes.ValueNode; +import org.graalvm.compiler.nodes.extended.UnsafeAccessNode; import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin; import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin; import org.graalvm.compiler.nodes.graphbuilderconf.LoopExplosionPlugin; import org.graalvm.compiler.nodes.java.MethodCallTargetNode; +import org.graalvm.compiler.nodes.type.StampTool; import org.graalvm.compiler.nodes.util.GraphUtil; import org.graalvm.compiler.replacements.PEGraphDecoder; import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; @@ -53,11 +55,16 @@ import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; +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.HostedProviders; import com.oracle.graal.pointsto.util.AnalysisError; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; public class InlineBeforeAnalysisGraphDecoder extends PEGraphDecoder { @@ -132,8 +139,11 @@ protected Node addFloatingNode(MethodScope methodScope, LoopScope loopScope, Nod @Override protected final Node canonicalizeFixedNode(MethodScope methodScope, LoopScope loopScope, Node node) { - Node canonical = super.canonicalizeFixedNode(methodScope, loopScope, node); - canonical = doCanonicalizeFixedNode(cast(methodScope), loopScope, canonical); + Node canonical = node; + if (node instanceof UnsafeAccessNode unsafeAccess) { + canonical = canonicalizeUnsafeAccess(unsafeAccess); + } + canonical = super.canonicalizeFixedNode(methodScope, loopScope, canonical); /* * When no canonicalization was done, we check the node that was decoded (which is already * alive, but we know it was just decoded and therefore not checked yet). @@ -147,9 +157,48 @@ protected final Node canonicalizeFixedNode(MethodScope methodScope, LoopScope lo return canonical; } - @SuppressWarnings("unused") - protected Node doCanonicalizeFixedNode(InlineBeforeAnalysisMethodScope methodScope, LoopScope loopScope, Node node) { - return node; + /** + * Method handles do unsafe field accesses with offsets from the hosting VM which we need to + * transform to field accesses to intrinsify method handle calls in an effective way (or at all, + * even). {@link UnsafeAccessNode#canonical} would call {@link AnalysisField#getOffset}, which + * is deemed not safe in the general case and therefore not allowed. Here, however, we execute + * before analysis and can assume that any field offsets originate in the hosting VM because we + * do not assign our field offsets until after the analysis. Therefore, we can transform unsafe + * accesses by accessing the hosting VM's types and fields, using code adapted from + * {@link UnsafeAccessNode#canonical}. We cannot do the same for arrays because array offsets + * with our own object layout can be computed early on (using {@code Unsafe}, even). + */ + private Node canonicalizeUnsafeAccess(UnsafeAccessNode node) { + if (!node.isCanonicalizable()) { + return node; + } + JavaConstant offset = node.offset().asJavaConstant(); + JavaConstant object = node.object().asJavaConstant(); + if (offset == null || object == null) { + return node; + } + AnalysisType objectType = (AnalysisType) StampTool.typeOrNull(node.object()); + if (objectType == null || objectType.isArray()) { + return node; + } + AnalysisType objectAsType = (AnalysisType) bb.getConstantReflectionProvider().asJavaType(object); + if (objectAsType != null) { + // Note: using the Class object of a type as the base object for accesses + // of that type's static fields is a HotSpot implementation detail. + ResolvedJavaType objectAsHostType = bb.getHostVM().getOriginalHostType(objectAsType); + ResolvedJavaField hostField = UnsafeAccessNode.findStaticFieldWithOffset(objectAsHostType, offset.asLong(), node.accessKind()); + return canonicalizeUnsafeAccessToField(node, hostField); + } + ResolvedJavaType objectHostType = bb.getHostVM().getOriginalHostType(objectType); + ResolvedJavaField hostField = objectHostType.findInstanceFieldWithOffset(offset.asLong(), node.accessKind()); + return canonicalizeUnsafeAccessToField(node, hostField); + } + + private ValueNode canonicalizeUnsafeAccessToField(UnsafeAccessNode node, ResolvedJavaField unwrappedField) { + AnalysisError.guarantee(unwrappedField != null, "Unsafe access to object header?"); + AnalysisField field = bb.getUniverse().lookup(unwrappedField); + AnalysisError.guarantee(!field.isInternal() && field.getJavaKind() == node.accessKind()); + return node.cloneAsFieldAccess(field); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index 3852908c7e17..b1d294bc0910 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -49,8 +49,8 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiPredicate; -import java.util.function.Function; import java.util.function.BooleanSupplier; +import java.util.function.Function; import org.graalvm.compiler.core.common.spi.ForeignCallDescriptor; import org.graalvm.compiler.core.common.spi.ForeignCallsProvider; @@ -137,6 +137,7 @@ import com.oracle.svm.hosted.phases.ImplicitAssertionsPhase; import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyImpl; import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyUtils; +import com.oracle.svm.hosted.substitute.SubstitutionType; import com.oracle.svm.hosted.substitute.UnsafeAutomaticSubstitutionProcessor; import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ReflectionUtil; @@ -1032,4 +1033,13 @@ private static Class[] extractAnnotationTypes(AnalysisField field, Class[] } return annotationTypes.toArray(new Class[0]); } + + @Override + public ResolvedJavaType getOriginalHostType(AnalysisType type) { + ResolvedJavaType unwrapped = super.getOriginalHostType(type); + if (unwrapped instanceof SubstitutionType substituted) { + return substituted.getOriginal(); + } + return unwrapped; + } } From 32ee19018a3f7e0433bc5bf0b0e9426d666d114b Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 15 Jun 2023 19:13:00 +0200 Subject: [PATCH 06/33] Restore method handles in ReflectionMetadataDecoderImpl. --- .../target/ReflectionMetadataDecoderImpl.java | 91 +++---------------- 1 file changed, 13 insertions(+), 78 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java index 5d88462d690f..955060a616ab 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionMetadataDecoderImpl.java @@ -106,23 +106,13 @@ static byte[] getEncoding() { @Override public Field[] parseFields(DynamicHub declaringType, int index, boolean publicOnly) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Field.class, new Function<>() { - @Override - public Field apply(Integer i) { - return (Field) decodeField(reader, DynamicHub.toClass(declaringType), publicOnly, true); - } - }); + return decodeArray(reader, Field.class, (i) -> (Field) decodeField(reader, DynamicHub.toClass(declaringType), publicOnly, true)); } @Override public FieldDescriptor[] parseReachableFields(DynamicHub declaringType, int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, FieldDescriptor.class, new Function<>() { - @Override - public FieldDescriptor apply(Integer i) { - return (FieldDescriptor) decodeField(reader, DynamicHub.toClass(declaringType), false, false); - } - }); + return decodeArray(reader, FieldDescriptor.class, (i) -> (FieldDescriptor) decodeField(reader, DynamicHub.toClass(declaringType), false, false)); } /** @@ -135,23 +125,13 @@ public FieldDescriptor apply(Integer i) { @Override public Method[] parseMethods(DynamicHub declaringType, int index, boolean publicOnly) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Method.class, new Function<>() { - @Override - public Method apply(Integer i) { - return (Method) decodeExecutable(reader, DynamicHub.toClass(declaringType), publicOnly, true, true); - } - }); + return decodeArray(reader, Method.class, (i) -> (Method) decodeExecutable(reader, DynamicHub.toClass(declaringType), publicOnly, true, true)); } @Override public MethodDescriptor[] parseReachableMethods(DynamicHub declaringType, int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, MethodDescriptor.class, new Function<>() { - @Override - public MethodDescriptor apply(Integer i) { - return (MethodDescriptor) decodeExecutable(reader, DynamicHub.toClass(declaringType), false, false, true); - } - }); + return decodeArray(reader, MethodDescriptor.class, (i) -> (MethodDescriptor) decodeExecutable(reader, DynamicHub.toClass(declaringType), false, false, true)); } /** @@ -164,23 +144,13 @@ public MethodDescriptor apply(Integer i) { @Override public Constructor[] parseConstructors(DynamicHub declaringType, int index, boolean publicOnly) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Constructor.class, new Function<>() { - @Override - public Constructor apply(Integer i) { - return (Constructor) decodeExecutable(reader, DynamicHub.toClass(declaringType), publicOnly, true, false); - } - }); + return decodeArray(reader, Constructor.class, (i) -> (Constructor) decodeExecutable(reader, DynamicHub.toClass(declaringType), publicOnly, true, false)); } @Override public ConstructorDescriptor[] parseReachableConstructors(DynamicHub declaringType, int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, ConstructorDescriptor.class, new Function<>() { - @Override - public ConstructorDescriptor apply(Integer i) { - return (ConstructorDescriptor) decodeExecutable(reader, DynamicHub.toClass(declaringType), false, false, false); - } - }); + return decodeArray(reader, ConstructorDescriptor.class, (i) -> (ConstructorDescriptor) decodeExecutable(reader, DynamicHub.toClass(declaringType), false, false, false)); } /** @@ -193,12 +163,7 @@ public ConstructorDescriptor apply(Integer i) { @Override public Class[] parseClasses(int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Class.class, new Function<>() { - @Override - public Class apply(Integer i) { - return decodeType(reader); - } - }); + return decodeArray(reader, Class.class, (i) -> decodeType(reader)); } /** @@ -211,12 +176,7 @@ public Class apply(Integer i) { @Override public Target_java_lang_reflect_RecordComponent[] parseRecordComponents(DynamicHub declaringType, int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Target_java_lang_reflect_RecordComponent.class, new Function<>() { - @Override - public Target_java_lang_reflect_RecordComponent apply(Integer i) { - return decodeRecordComponent(reader, DynamicHub.toClass(declaringType)); - } - }); + return decodeArray(reader, Target_java_lang_reflect_RecordComponent.class, (i) -> decodeRecordComponent(reader, DynamicHub.toClass(declaringType))); } /** @@ -229,12 +189,7 @@ public Target_java_lang_reflect_RecordComponent apply(Integer i) { @Override public Object[] parseObjects(int index) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(getEncoding(), index, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Object.class, new Function<>() { - @Override - public Object apply(Integer i) { - return decodeObject(reader); - } - }); + return decodeArray(reader, Object.class, (i) -> decodeObject(reader)); } /** @@ -247,12 +202,7 @@ public Object apply(Integer i) { @Override public Parameter[] parseReflectParameters(Executable executable, byte[] encoding) { UnsafeArrayTypeReader reader = UnsafeArrayTypeReader.create(encoding, 0, ByteArrayReader.supportsUnalignedMemoryAccess()); - return decodeArray(reader, Parameter.class, new Function<>() { - @Override - public Parameter apply(Integer i) { - return decodeReflectParameter(reader, executable, i); - } - }); + return decodeArray(reader, Parameter.class, (i) -> decodeReflectParameter(reader, executable, i)); } /** @@ -556,19 +506,9 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class decla String name = isMethod ? decodeName(buf) : null; Object[] parameterTypes; if (complete || hiding || negative) { - parameterTypes = decodeArray(buf, Class.class, new Function<>() { - @Override - public Class apply(Integer i) { - return decodeType(buf); - } - }); + parameterTypes = decodeArray(buf, Class.class, (i) -> decodeType(buf)); } else { - parameterTypes = decodeArray(buf, String.class, new Function<>() { - @Override - public String apply(Integer i) { - return decodeName(buf); - } - }); + parameterTypes = decodeArray(buf, String.class, (i) -> decodeName(buf)); } Class returnType = isMethod && (complete || hiding) ? decodeType(buf) : null; if (!complete) { @@ -596,12 +536,7 @@ public String apply(Integer i) { return SubstrateUtil.cast(constructor, Executable.class); } } - Class[] exceptionTypes = decodeArray(buf, Class.class, new Function<>() { - @Override - public Class apply(Integer i) { - return decodeType(buf); - } - }); + Class[] exceptionTypes = decodeArray(buf, Class.class, (i) -> decodeType(buf)); String signature = decodeName(buf); byte[] annotations = decodeByteArray(buf); byte[] parameterAnnotations = decodeByteArray(buf); From f91aa1133197817ba92433c61e74a5626087527d Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Fri, 16 Jun 2023 13:38:00 +0200 Subject: [PATCH 07/33] Add plugins and final attributes to foster folding method handles. --- .../oracle/svm/core/jdk/VarHandleFeature.java | 40 ++++++++++-------- ...et_java_lang_invoke_BoundMethodHandle.java | 5 ++- .../Target_java_lang_invoke_MethodHandle.java | 18 +++++++- ..._java_lang_invoke_MethodHandleNatives.java | 42 ++++++++++--------- ...java_lang_invoke_MethodHandles_Lookup.java | 7 ++-- .../Target_java_lang_invoke_MethodType.java | 2 +- ...arget_java_lang_invoke_MethodTypeForm.java | 4 +- ...ang_invoke_VarHandle_AccessDescriptor.java | 6 ++- .../InlineBeforeAnalysisPolicyUtils.java | 14 +------ .../hosted/snippets/ReflectionPlugins.java | 9 +++- 10 files changed, 83 insertions(+), 64 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java index 54c8944990c5..aadf76297688 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java @@ -251,73 +251,73 @@ static void set(Object varHandle, Object value) { */ @TargetClass(className = "java.lang.invoke.VarHandleBooleans", innerClass = "Array") final class Target_java_lang_invoke_VarHandleBooleans_Array { - @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = boolean[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = boolean[].class, isFinal = true) // int abase; - @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = boolean[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = boolean[].class, isFinal = true) // int ashift; } @TargetClass(className = "java.lang.invoke.VarHandleBytes", innerClass = "Array") final class Target_java_lang_invoke_VarHandleBytes_Array { - @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = byte[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = byte[].class, isFinal = true) // int abase; - @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = byte[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = byte[].class, isFinal = true) // int ashift; } @TargetClass(className = "java.lang.invoke.VarHandleChars", innerClass = "Array") final class Target_java_lang_invoke_VarHandleChars_Array { - @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = char[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = char[].class, isFinal = true) // int abase; - @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = char[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = char[].class, isFinal = true) // int ashift; } @TargetClass(className = "java.lang.invoke.VarHandleDoubles", innerClass = "Array") final class Target_java_lang_invoke_VarHandleDoubles_Array { - @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = double[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = double[].class, isFinal = true) // int abase; - @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = double[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = double[].class, isFinal = true) // int ashift; } @TargetClass(className = "java.lang.invoke.VarHandleFloats", innerClass = "Array") final class Target_java_lang_invoke_VarHandleFloats_Array { - @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = float[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = float[].class, isFinal = true) // int abase; - @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = float[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = float[].class, isFinal = true) // int ashift; } @TargetClass(className = "java.lang.invoke.VarHandleInts", innerClass = "Array") final class Target_java_lang_invoke_VarHandleInts_Array { - @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = int[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = int[].class, isFinal = true) // int abase; - @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = int[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = int[].class, isFinal = true) // int ashift; } @TargetClass(className = "java.lang.invoke.VarHandleLongs", innerClass = "Array") final class Target_java_lang_invoke_VarHandleLongs_Array { - @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = long[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = long[].class, isFinal = true) // int abase; - @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = long[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = long[].class, isFinal = true) // int ashift; } @TargetClass(className = "java.lang.invoke.VarHandleShorts", innerClass = "Array") final class Target_java_lang_invoke_VarHandleShorts_Array { - @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = short[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = short[].class, isFinal = true) // int abase; - @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = short[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = short[].class, isFinal = true) // int ashift; } @TargetClass(className = "java.lang.invoke.VarHandleReferences", innerClass = "Array") final class Target_java_lang_invoke_VarHandleReferences_Array { - @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = Object[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayBaseOffset, declClass = Object[].class, isFinal = true) // int abase; - @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = Object[].class) // + @Alias @RecomputeFieldValue(kind = Kind.ArrayIndexShift, declClass = Object[].class, isFinal = true) // int ashift; } @@ -325,6 +325,10 @@ final class Target_java_lang_invoke_VarHandleReferences_Array { * Substitutions for VarHandle instance field access classes. They all follow the same pattern: they * store the receiver type (no need to recompute that) and the field offset (we need to recompute * that). + * + * Because the offset field values can be set only after instance field offsets are assigned + * following the analysis, the unsafe accesses cannot be constant-folded unless inlining before + * analysis is already successful in transforming them to a field access. */ @TargetClass(className = "java.lang.invoke.VarHandleBooleans", innerClass = "FieldInstanceReadOnly") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_BoundMethodHandle.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_BoundMethodHandle.java index abe25c22b7ab..52ef0fa8e796 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_BoundMethodHandle.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_BoundMethodHandle.java @@ -58,7 +58,8 @@ */ @TargetClass(className = "java.lang.invoke.BoundMethodHandle") final class Target_java_lang_invoke_BoundMethodHandle { - @Alias static Target_java_lang_invoke_BoundMethodHandle_Specializer SPECIALIZER; + @Alias @RecomputeFieldValue(isFinal = true, kind = RecomputeFieldValue.Kind.None) // + static Target_java_lang_invoke_BoundMethodHandle_Specializer SPECIALIZER; @Alias @TargetElement(name = CONSTRUCTOR_NAME) @@ -139,7 +140,7 @@ static Target_java_lang_invoke_BoundMethodHandle make(MethodType mt, Target_java @TargetClass(className = "java.lang.invoke.BoundMethodHandle", innerClass = "SpeciesData") final class Target_java_lang_invoke_BoundMethodHandle_SpeciesData { - @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = NewEmptyArrayFieldValueTransformer.class) // + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = NewEmptyArrayFieldValueTransformer.class, isFinal = true) // private Target_java_lang_invoke_BoundMethodHandle_SpeciesData[] extensions; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java index 78bcecbadfe4..667c47c0c730 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java @@ -35,6 +35,7 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; @@ -60,7 +61,8 @@ final class Target_java_lang_invoke_MethodHandle { @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // private MethodHandle asTypeCache; - @Alias MethodType type; + @Alias @RecomputeFieldValue(isFinal = true, kind = RecomputeFieldValue.Kind.None) // + MethodType type; @Alias native Target_java_lang_invoke_MemberName internalMemberName(); @@ -115,6 +117,17 @@ static Object linkToInterface(Object... args) throws Throwable { static Object linkToSpecial(Object... args) throws Throwable { return Util_java_lang_invoke_MethodHandle.linkTo(true, args); } + + @Substitute + void maybeCustomize() { + /* + * JDK 8 update 60 added an additional customization possibility for method handles. For all + * use cases that we care about, that seems to be unnecessary, so we can just do nothing. + */ + } + + @Delete + native void customize(); } final class Util_java_lang_invoke_MethodHandle { @@ -235,7 +248,8 @@ static Object invokeInternal(Target_java_lang_invoke_MemberName memberName, Meth @TargetClass(className = "java.lang.invoke.DirectMethodHandle") final class Target_java_lang_invoke_DirectMethodHandle { - @Alias Target_java_lang_invoke_MemberName member; + @Alias @RecomputeFieldValue(isFinal = true, kind = RecomputeFieldValue.Kind.None) // + Target_java_lang_invoke_MemberName member; @Substitute void ensureInitialized() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java index ed14360045d5..4ddba67980c8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java @@ -45,6 +45,8 @@ import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.AnnotateOriginal; import com.oracle.svm.core.annotate.Delete; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; @@ -362,34 +364,34 @@ static boolean verifyAccess(Class refc, Class defc, int mods, Class loo @TargetClass(className = "java.lang.invoke.MethodHandleNatives", innerClass = "Constants") final class Target_java_lang_invoke_MethodHandleNatives_Constants { // Checkstyle: stop - @Alias static int MN_IS_METHOD; - @Alias static int MN_IS_CONSTRUCTOR; - @Alias static int MN_IS_FIELD; - @Alias static int MN_IS_TYPE; - @Alias static int MN_CALLER_SENSITIVE; - @Alias static int MN_REFERENCE_KIND_SHIFT; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static int MN_IS_METHOD; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static int MN_IS_CONSTRUCTOR; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static int MN_IS_FIELD; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static int MN_IS_TYPE; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static int MN_CALLER_SENSITIVE; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static int MN_REFERENCE_KIND_SHIFT; @TargetElement(onlyWith = {JDK20OrEarlier.class})// - @Alias static int MN_REFERENCE_KIND_MASK; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static int MN_REFERENCE_KIND_MASK; // The SEARCH_* bits are not for MN.flags but for the matchFlags argument of MHN.getMembers: @TargetElement(onlyWith = {JDK20OrEarlier.class})// - @Alias static int MN_SEARCH_SUPERCLASSES; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static int MN_SEARCH_SUPERCLASSES; @TargetElement(onlyWith = {JDK20OrEarlier.class})// - @Alias static int MN_SEARCH_INTERFACES; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static int MN_SEARCH_INTERFACES; /** * Constant pool reference-kind codes, as used by CONSTANT_MethodHandle CP entries. */ - @Alias static byte REF_NONE; // null value - @Alias static byte REF_getField; - @Alias static byte REF_getStatic; - @Alias static byte REF_putField; - @Alias static byte REF_putStatic; - @Alias static byte REF_invokeVirtual; - @Alias static byte REF_invokeStatic; - @Alias static byte REF_invokeSpecial; - @Alias static byte REF_newInvokeSpecial; - @Alias static byte REF_invokeInterface; - @Alias static byte REF_LIMIT; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static byte REF_NONE; // null + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static byte REF_getField; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static byte REF_getStatic; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static byte REF_putField; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static byte REF_putStatic; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static byte REF_invokeVirtual; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static byte REF_invokeStatic; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static byte REF_invokeSpecial; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static byte REF_newInvokeSpecial; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static byte REF_invokeInterface; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) static byte REF_LIMIT; // Checkstyle: resume } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java index 82d3589af7bd..5f28bed354b9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java @@ -31,6 +31,7 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.hub.DynamicHub; @@ -53,13 +54,13 @@ private MethodHandle maybeBindCaller(Target_java_lang_invoke_MemberName method, return mh; } - @Alias // + @Alias @RecomputeFieldValue(isFinal = true, kind = RecomputeFieldValue.Kind.None) // private Class lookupClass; - @Alias // + @Alias @RecomputeFieldValue(isFinal = true, kind = RecomputeFieldValue.Kind.None) // private Class prevLookupClass; - @Alias // + @Alias @RecomputeFieldValue(isFinal = true, kind = RecomputeFieldValue.Kind.None) // private int allowedModes; @Substitute diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodType.java index 3d1e8e53f58e..19e26d9f5091 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodType.java @@ -45,7 +45,7 @@ final class Target_java_lang_invoke_MethodType { * Since MethodHandle is not supported yet at run time, we could also disable the usage of * MethodType completely. But this recomputation seems less intrusive. */ - @Alias @RecomputeFieldValue(kind = Kind.NewInstance, declClassName = "java.lang.invoke.MethodType$ConcurrentWeakInternSet") // + @Alias @RecomputeFieldValue(kind = Kind.NewInstance, declClassName = "java.lang.invoke.MethodType$ConcurrentWeakInternSet", isFinal = true) // static Target_java_lang_invoke_MethodType_ConcurrentWeakInternSet internTable; /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodTypeForm.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodTypeForm.java index 083b86a8f715..3bc71cb1b467 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodTypeForm.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodTypeForm.java @@ -40,8 +40,8 @@ final class Target_java_lang_invoke_MethodTypeForm { * consistent state, to avoid problems when the lazily initialization happens during image heap * writing. */ - @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = NewEmptyArrayFieldValueTransformer.class) // + @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = NewEmptyArrayFieldValueTransformer.class, isFinal = true) // private SoftReference[] methodHandles; - @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = NewEmptyArrayFieldValueTransformer.class) // + @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = NewEmptyArrayFieldValueTransformer.class, isFinal = true) // private SoftReference[] lambdaForms; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_VarHandle_AccessDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_VarHandle_AccessDescriptor.java index 22f2d7bb9aee..ae37acd73beb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_VarHandle_AccessDescriptor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_VarHandle_AccessDescriptor.java @@ -27,12 +27,14 @@ import java.lang.invoke.MethodType; import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.TargetClass; @TargetClass(className = "java.lang.invoke.VarHandle", innerClass = "AccessDescriptor") final class Target_java_lang_invoke_VarHandle_AccessDescriptor { - @Alias// + @Alias @RecomputeFieldValue(isFinal = true, kind = RecomputeFieldValue.Kind.None) // MethodType symbolicMethodTypeInvoker; - @Alias// + + @Alias @RecomputeFieldValue(isFinal = true, kind = RecomputeFieldValue.Kind.None) // int mode; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java index d4ba3aaff8ae..b66ab5602571 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java @@ -457,19 +457,7 @@ public String toString() { private static boolean inlineForMethodHandleIntrinsification(ResolvedJavaMethod method) { String className = method.getDeclaringClass().toJavaName(true); - if (className.startsWith("java.lang.invoke.VarHandle") && (!className.equals("java.lang.invoke.VarHandle") || method.getName().equals("getMethodHandleUncached"))) { - /* - * Do not inline implementation methods of various VarHandle implementation classes. - * They are too complex and cannot be reduced to a single invoke or field access. There - * is also no need to inline them, because they are not related to any MethodHandle - * mechanism. - * - * Methods defined in VarHandle itself are fine and not covered by this rule, apart from - * well-known methods that are never useful to be inlined. If these methods are reached, - * intrinsification will not be possible in any case. - */ - return false; - } else if (className.startsWith("java.lang.invoke") && !className.contains("InvokerBytecodeGenerator")) { + if (className.startsWith("java.lang.invoke") && !className.contains("InvokerBytecodeGenerator")) { /* * Inline all helper methods used by method handles. We do not know exactly which ones * they are, but they are all from the same package. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java index 23513e435b74..ac4b9216c860 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java @@ -155,6 +155,9 @@ public static void registerInvocationPlugins(ImageClassLoader imageClassLoader, rp.registerClassPlugins(plugins); } + private static final Class VAR_FORM_CLASS = ReflectionUtil.lookupClass(false, "java.lang.invoke.VarForm"); + private static final Class MEMBER_NAME_CLASS = ReflectionUtil.lookupClass(false, "java.lang.invoke.MemberName"); + /** * Classes that are allowed to be constant folded for Object parameters. We must be careful and * return only objects of classes that are "immutable enough", i.e., cannot change their @@ -173,7 +176,7 @@ public static void registerInvocationPlugins(ImageClassLoader imageClassLoader, Class.class, String.class, ClassLoader.class, Method.class, Constructor.class, Field.class, MethodHandle.class, MethodHandles.Lookup.class, MethodType.class, - VarHandle.class, + VarHandle.class, VAR_FORM_CLASS, MEMBER_NAME_CLASS, ByteOrder.class); private void registerMethodHandlesPlugins(InvocationPlugins plugins) { @@ -201,6 +204,10 @@ private void registerMethodHandlesPlugins(InvocationPlugins plugins) { "changeReturnType", "erase", "generic", "wrap", "unwrap", "parameterType", "parameterCount", "returnType", "lastParameterType"); + registerFoldInvocationPlugins(plugins, MethodHandle.class, "asType"); + + registerFoldInvocationPlugins(plugins, VAR_FORM_CLASS, "resolveMemberName"); + registerConditionalFoldInvocationPlugins(plugins); Registration r = new Registration(plugins, MethodHandles.class); From 890d5ac8c193ce0a412ae27de879421002cc1f35 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Mon, 19 Jun 2023 18:56:23 +0200 Subject: [PATCH 08/33] Remove currently unneeded recursive abortion of method handle intrinsification. --- .../InlineBeforeAnalysisPolicyUtils.java | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java index b66ab5602571..98291a3801db 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java @@ -280,8 +280,6 @@ public final class AccumulativeInlineScope extends InlineBeforeAnalysisPolicy.Ab int numInvokes = 0; Boolean lenientForMethodHandleIntrinsic = null; // lazily initialized - boolean calleeMethodHandleIntrinsicFailed = false; - boolean methodHandleIntrinsicFailed = false; AccumulativeInlineScope(AccumulativeCounters accumulativeCounters, int inliningDepth) { super(inliningDepth); @@ -320,7 +318,6 @@ public void abortCalleeScope(InlineBeforeAnalysisPolicy.AbstractPolicyScope call } else { assert !accumulativeCounters.inMethodHandleIntrinsification && calleeScope.accumulativeCounters.inMethodHandleIntrinsification; } - calleeMethodHandleIntrinsicFailed = calleeScope.methodHandleIntrinsicFailed; } @Override @@ -343,12 +340,6 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met return false; } - if (lenientForMethodHandleIntrinsic == Boolean.TRUE && calleeMethodHandleIntrinsicFailed) { - assert !methodHandleIntrinsicFailed : "must have failed earlier"; - methodHandleIntrinsicFailed = true; // propagates to caller - return false; - } - if (node instanceof FullInfopointNode || node instanceof ValueProxy || node instanceof ValueAnchorNode || node instanceof FrameState || node instanceof AbstractBeginNode || node instanceof AbstractEndNode) { /* @@ -432,20 +423,13 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met if (lenientForMethodHandleIntrinsic == null) { lenientForMethodHandleIntrinsic = inlineForMethodHandleIntrinsification(method); } - if (lenientForMethodHandleIntrinsic) { - if (calleeMethodHandleIntrinsicFailed) { - methodHandleIntrinsicFailed = true; // propagates to caller - return false; - } - allow = true; - } + allow = lenientForMethodHandleIntrinsic; } return allow; } @Override protected boolean shouldInterpretMethodHandleInvoke(ResolvedJavaMethod method, MethodHandleWithExceptionNode node) { - methodHandleIntrinsicFailed = true; return false; } From 2b6d0d1cfd37df36926f7fee95c362d26a060043 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 21 Jun 2023 13:55:40 +0200 Subject: [PATCH 09/33] Do not use word types in lambdas. --- .../graalvm/nativebridge/JNIClassCache.java | 28 ++++---- .../phases/SharedGraphBuilderPhase.java | 4 +- .../IsolatedTruffleRuntimeSupport.java | 11 ++-- .../polyglot/nativeapi/PolyglotNativeAPI.java | 66 +++++++++++-------- 4 files changed, 62 insertions(+), 47 deletions(-) diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/nativebridge/JNIClassCache.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/nativebridge/JNIClassCache.java index 36f1ac494be8..728118744354 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/nativebridge/JNIClassCache.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/nativebridge/JNIClassCache.java @@ -24,16 +24,17 @@ */ package org.graalvm.nativebridge; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + import org.graalvm.jniutils.JNI.JClass; import org.graalvm.jniutils.JNI.JNIEnv; import org.graalvm.jniutils.JNIExceptionWrapper; import org.graalvm.jniutils.JNIUtil; import org.graalvm.word.WordFactory; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - /** * Support class for {@link JClass} lookup. JClass instances are cached as JNI globals. The cached * JNI globals are disposed by {@link JNIClassCache#dispose(JNIEnv)}. @@ -68,16 +69,17 @@ public static JClass lookupClass(JNIEnv env, String className) throws JNIExcepti } private static JClass lookupClassImpl(JNIEnv env, String className, boolean required) { - return classesByName.computeIfAbsent(className, (cn) -> { - JClass jClass = JNIUtil.findClass(env, WordFactory.nullPointer(), JNIUtil.getBinaryName(className), required); - JNIClassData res; - if (jClass.isNull()) { - res = JNIClassData.INVALID; - } else { - res = new JNIClassData(JNIUtil.NewGlobalRef(env, jClass, "Class<" + className + ">")); + Function createClassData = new Function<>() { + @Override + public JNIClassData apply(String cn) { + JClass jClass = JNIUtil.findClass(env, WordFactory.nullPointer(), JNIUtil.getBinaryName(className), required); + if (jClass.isNull()) { + return JNIClassData.INVALID; + } + return new JNIClassData(JNIUtil.NewGlobalRef(env, jClass, "Class<" + className + ">")); } - return res; - }).jClassGlobal; + }; + return classesByName.computeIfAbsent(className, createClassData).jClassGlobal; } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java index a7e6ff70c6eb..ebf2f705ce11 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java @@ -596,7 +596,9 @@ private void checkWordType(ValueNode value, JavaType expectedType, String reason if (isWordTypeExpected && !isWordValue) { throw UserError.abort("Expected Word but got Object for %s in %s", reason, method.asStackTraceElement(bci())); } else if (!isWordTypeExpected && isWordValue) { - throw UserError.abort("Expected Object but got Word for %s in %s", reason, method.asStackTraceElement(bci())); + throw UserError.abort("Expected Object but got Word for %s in %s. One possible cause for this error is when word values are passed into lambdas as parameters " + + "or from variables in an enclosing scope, which is not supported, but can be solved by instead using explicit classes (including anonymous classes).", + reason, method.asStackTraceElement(bci())); } } } diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedTruffleRuntimeSupport.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedTruffleRuntimeSupport.java index 64b59f8eb2bc..7d38a5a2ddf5 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedTruffleRuntimeSupport.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedTruffleRuntimeSupport.java @@ -27,8 +27,8 @@ import java.util.function.Consumer; import java.util.function.Supplier; -import org.graalvm.compiler.truffle.common.TruffleCompilable; import org.graalvm.compiler.truffle.common.OptimizedAssumptionDependency; +import org.graalvm.compiler.truffle.common.TruffleCompilable; import org.graalvm.compiler.truffle.runtime.OptimizedAssumption; import org.graalvm.compiler.truffle.runtime.OptimizedCallTarget; import org.graalvm.compiler.truffle.runtime.OptimizedDirectCallNode; @@ -172,9 +172,12 @@ public static TriState tryIsSuppressedFailure(TruffleCompilable compilable, Supp @CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished) private static boolean isSuppressedFailure0(@SuppressWarnings("unused") ClientIsolateThread client, ClientHandle ast, CompilerHandle> serializedExceptionHandle) { - Supplier serializedException = () -> { - ClientHandle resultHandle = getReasonAndStackTrace0(IsolatedCompileClient.get().getCompiler(), serializedExceptionHandle); - return IsolatedCompileClient.get().unhand(resultHandle); + Supplier serializedException = new Supplier<>() { + @Override + public String get() { + ClientHandle resultHandle = getReasonAndStackTrace0(IsolatedCompileClient.get().getCompiler(), serializedExceptionHandle); + return IsolatedCompileClient.get().unhand(resultHandle); + } }; SubstrateTruffleRuntime runtime = (SubstrateTruffleRuntime) SubstrateTruffleRuntime.getRuntime(); return runtime.isSuppressedFailure(IsolatedCompileClient.get().unhand(ast), serializedException); diff --git a/substratevm/src/org.graalvm.polyglot.nativeapi/src/org/graalvm/polyglot/nativeapi/PolyglotNativeAPI.java b/substratevm/src/org.graalvm.polyglot.nativeapi/src/org/graalvm/polyglot/nativeapi/PolyglotNativeAPI.java index 4d53e68ff866..f83bf110ab8a 100644 --- a/substratevm/src/org.graalvm.polyglot.nativeapi/src/org/graalvm/polyglot/nativeapi/PolyglotNativeAPI.java +++ b/substratevm/src/org.graalvm.polyglot.nativeapi/src/org/graalvm/polyglot/nativeapi/PolyglotNativeAPI.java @@ -53,6 +53,7 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.ObjectHandle; import org.graalvm.nativeimage.Threading; +import org.graalvm.nativeimage.Threading.RecurringCallback; import org.graalvm.nativeimage.UnmanagedMemory; import org.graalvm.nativeimage.VMRuntime; import org.graalvm.nativeimage.c.CHeader; @@ -1865,24 +1866,27 @@ public static PolyglotStatus poly_create_function(PolyglotIsolateThread thread, nullCheck(callback, "callback"); nullCheck(value, "value"); Context c = fetchHandle(context); - ProxyExecutable executable = (Value... arguments) -> { - int frame = getHandles().pushFrame(DEFAULT_FRAME_CAPACITY); - try { - ObjectHandle[] handleArgs = new ObjectHandle[arguments.length]; - for (int i = 0; i < arguments.length; i++) { - handleArgs[i] = createHandle(arguments[i]); - } - PolyglotCallbackInfo cbInfo = (PolyglotCallbackInfo) createHandle(new PolyglotCallbackInfoInternal(handleArgs, data)); - PolyglotValue result = callback.invoke((PolyglotIsolateThread) CurrentIsolate.getCurrentThread(), cbInfo); - CallbackException ce = exceptionsTL.get(); - if (ce != null) { - exceptionsTL.remove(); - throw ce; - } else { - return PolyglotNativeAPI.fetchHandle(result); + ProxyExecutable executable = new ProxyExecutable() { + @Override + public Object execute(Value... arguments) { + int frame = getHandles().pushFrame(DEFAULT_FRAME_CAPACITY); + try { + ObjectHandle[] handleArgs = new ObjectHandle[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + handleArgs[i] = createHandle(arguments[i]); + } + PolyglotCallbackInfo cbInfo = (PolyglotCallbackInfo) createHandle(new PolyglotCallbackInfoInternal(handleArgs, data)); + PolyglotValue result = callback.invoke((PolyglotIsolateThread) CurrentIsolate.getCurrentThread(), cbInfo); + CallbackException ce = exceptionsTL.get(); + if (ce != null) { + exceptionsTL.remove(); + throw ce; + } else { + return PolyglotNativeAPI.fetchHandle(result); + } + } finally { + getHandles().popFramesIncluding(frame); } - } finally { - getHandles().popFramesIncluding(frame); } }; value.write(createHandle(c.asValue(executable))); @@ -2295,20 +2299,24 @@ private static PolyglotStatus doRegisterRecurringCallback0(long intervalNanos, P return poly_ok; } PolyglotCallbackInfoInternal info = new PolyglotCallbackInfoInternal(new ObjectHandle[0], data); - Threading.registerRecurringCallback(intervalNanos, TimeUnit.NANOSECONDS, access -> { - int frame = getHandles().pushFrame(DEFAULT_FRAME_CAPACITY); - try { - PolyglotCallbackInfo infoHandle = (PolyglotCallbackInfo) createHandle(info); - callback.invoke((PolyglotIsolateThread) CurrentIsolate.getCurrentThread(), infoHandle); - CallbackException ce = exceptionsTL.get(); - if (ce != null) { - exceptionsTL.remove(); - access.throwException(ce); + RecurringCallback recurringCallback = new RecurringCallback() { + @Override + public void run(Threading.RecurringCallbackAccess access) { + int frame = getHandles().pushFrame(DEFAULT_FRAME_CAPACITY); + try { + PolyglotCallbackInfo infoHandle = (PolyglotCallbackInfo) createHandle(info); + callback.invoke((PolyglotIsolateThread) CurrentIsolate.getCurrentThread(), infoHandle); + CallbackException ce = exceptionsTL.get(); + if (ce != null) { + exceptionsTL.remove(); + access.throwException(ce); + } + } finally { + getHandles().popFramesIncluding(frame); } - } finally { - getHandles().popFramesIncluding(frame); } - }); + }; + Threading.registerRecurringCallback(intervalNanos, TimeUnit.NANOSECONDS, recurringCallback); return poly_ok; } From 6b7cd827a9170ebefacca5f94e9da319d0e30495 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Fri, 23 Jun 2023 10:58:51 +0200 Subject: [PATCH 10/33] Rebuild MethodType.internTable with reachable objects. --- .../Target_java_lang_invoke_MethodType.java | 52 ------------------- .../methodhandles/MethodHandleFeature.java | 36 +++++++++++-- 2 files changed, 33 insertions(+), 55 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodType.java index 19e26d9f5091..9b8cb547f194 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodType.java @@ -24,9 +24,7 @@ */ package com.oracle.svm.core.methodhandles; -import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; -import java.lang.invoke.WrongMethodTypeException; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; @@ -38,16 +36,6 @@ @TargetClass(java.lang.invoke.MethodType.class) final class Target_java_lang_invoke_MethodType { - /** - * This map contains MethodType instances that refer to classes of the image generator. Starting - * with a new empty set at run time avoids bringing over unnecessary cache entries. - * - * Since MethodHandle is not supported yet at run time, we could also disable the usage of - * MethodType completely. But this recomputation seems less intrusive. - */ - @Alias @RecomputeFieldValue(kind = Kind.NewInstance, declClassName = "java.lang.invoke.MethodType$ConcurrentWeakInternSet", isFinal = true) // - static Target_java_lang_invoke_MethodType_ConcurrentWeakInternSet internTable; - /** * This field is lazily initialized. We need a stable value, otherwise the initialization can * happen just during image heap writing. @@ -62,48 +50,8 @@ final class Target_java_lang_invoke_MethodType { private String methodDescriptor; } -@TargetClass(value = java.lang.invoke.MethodType.class, innerClass = "ConcurrentWeakInternSet") -final class Target_java_lang_invoke_MethodType_ConcurrentWeakInternSet { -} - -/** - * The substitutions are needed to replace identity comparison ({@code ==}) with - * {@code MethodType.equal} calls. We do not keep - * {@link Target_java_lang_invoke_MethodType#internTable}, so we cannot guarantee identity. - */ @TargetClass(className = "java.lang.invoke.Invokers") final class Target_java_lang_invoke_Invokers { - @Substitute - static void checkExactType(MethodHandle mh, MethodType expected) { - if (!expected.equals(mh.type())) { - throw new WrongMethodTypeException("Expected " + expected + " but found " + mh.type()); - } - } - - @Substitute - static MethodHandle checkVarHandleGenericType(Target_java_lang_invoke_VarHandle handle, Target_java_lang_invoke_VarHandle_AccessDescriptor ad) { - // Test for exact match on invoker types - // TODO match with erased types and add cast of return value to lambda form - MethodHandle mh = handle.getMethodHandle(ad.mode); - if (mh.type().equals(ad.symbolicMethodTypeInvoker)) { - return mh; - } else { - return mh.asType(ad.symbolicMethodTypeInvoker); - } - } - - @Substitute - static MethodHandle checkVarHandleExactType(Target_java_lang_invoke_VarHandle handle, Target_java_lang_invoke_VarHandle_AccessDescriptor ad) { - MethodHandle mh = handle.getMethodHandle(ad.mode); - MethodType mt = mh.type(); - if (!mt.equals(ad.symbolicMethodTypeInvoker)) { - throw newWrongMethodTypeException(mt, ad.symbolicMethodTypeInvoker); - } - return mh; - } - - @Alias - static native WrongMethodTypeException newWrongMethodTypeException(MethodType actual, MethodType expected); } @TargetClass(className = "java.lang.invoke.InvokerBytecodeGenerator") diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java index d098e8cf9631..89c6bf0b2fc0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java @@ -109,6 +109,16 @@ public class MethodHandleFeature implements InternalFeature { private Class classSpecializerClass; private Class arrayAccessorClass; + /** + * A new {@link MethodType} interning table which contains only objects that are already part of + * the image. We cannot replace it with an empty table like we do for other caches because the + * method handle code uses reference comparisons on {@link MethodType} objects and assumes that + * unidentical objects are not equal. This breaks if an object is created at runtime because an + * equivalent image heap object is not part of the table and subsequently fails a comparison. + */ + private Object runtimeMethodTypeInternTable; + private Method concurrentWeakInternSetAdd; + @Override public void duringSetup(DuringSetupAccess access) { seenMethodHandles = ConcurrentHashMap.newKeySet(); @@ -145,7 +155,11 @@ public void duringSetup(DuringSetupAccess access) { typedAccessors = ReflectionUtil.lookupField(arrayAccessorClass, "TYPED_ACCESSORS"); classSpecializerCache = ReflectionUtil.lookupField(classSpecializerClass, "cache"); - access.registerObjectReplacer(this::registerMethodHandle); + Class concurrentWeakInternSetClass = access.findClassByName("java.lang.invoke.MethodType$ConcurrentWeakInternSet"); + runtimeMethodTypeInternTable = ReflectionUtil.newInstance(concurrentWeakInternSetClass); + concurrentWeakInternSetAdd = ReflectionUtil.lookupMethod(concurrentWeakInternSetClass, "add", Object.class); + + access.registerObjectReplacer(this::registerSeenObject); } @Override @@ -229,6 +243,9 @@ private boolean isSpeciesReachable(Object speciesData) { return analysisType.isPresent() && analysisType.get().isReachable(); } }); + access.registerFieldValueTransformer( + ReflectionUtil.lookupField(ReflectionUtil.lookupClass(false, "java.lang.invoke.MethodType"), "internTable"), + (receiver, originalValue) -> runtimeMethodTypeInternTable); } private static void registerMHImplFunctionsForReflection(DuringAnalysisAccess access) { @@ -327,13 +344,25 @@ private static void registerVarHandleMethodsForReflection(FeatureAccess access, } } - private Object registerMethodHandle(Object obj) { + private Object registerSeenObject(Object obj) { if (!BuildPhaseProvider.isAnalysisFinished()) { - registerMethodHandleRecurse(obj); + if (obj instanceof MethodType) { + registerMethodType(obj); + } else { + registerMethodHandleRecurse(obj); + } } return obj; } + private void registerMethodType(Object obj) { + try { + concurrentWeakInternSetAdd.invoke(runtimeMethodTypeInternTable, obj); + } catch (ReflectiveOperationException e) { + throw VMError.shouldNotReachHere(e); + } + } + private void registerMethodHandleRecurse(Object obj) { if (!(obj instanceof MethodHandle) || seenMethodHandles.contains(obj)) { return; @@ -423,6 +452,7 @@ public void duringAnalysis(DuringAnalysisAccess a) { access.rescanRoot(lambdaFormNFIdentity); access.rescanRoot(lambdaFormNFZero); access.rescanRoot(typedAccessors); + access.rescanObject(runtimeMethodTypeInternTable); } private static void scanBoundMethodHandle(DuringAnalysisAccess a, Class bmhSubtype) { From c07fd0d4ce965f85a67aac9ebd04ac81f5b5ea32 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Sat, 1 Jul 2023 09:11:30 +0200 Subject: [PATCH 11/33] Try to interpret for unresolvable delegating method handles. --- .../Target_java_lang_invoke_MethodHandle.java | 25 +++++++++++++++++-- ..._java_lang_invoke_MethodHandleNatives.java | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java index 667c47c0c730..b49b1f982c1c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java @@ -75,9 +75,24 @@ final class Target_java_lang_invoke_MethodHandle { Object invokeBasic(Object... args) throws Throwable { Target_java_lang_invoke_MemberName memberName = internalMemberName(); Object ret; - if (memberName != null) { /* Direct method handle */ + if (memberName != null) { + /* Method handles associated with a member, typically direct method handles. */ + boolean delegates = Target_java_lang_invoke_DelegatingMethodHandle.class.isInstance(this); + if (delegates && Util_java_lang_invoke_MethodHandleNatives.resolve(memberName, null, true) == null) { + /* + * Method handles can get associated with a member that we cannot resolve and use + * directly, but interpreting the target handle's lambda form can still succeed. For + * example, VarHandles create MethodHandleImpl.WrappedMember method handle objects + * that are associated with universal methods such as VarHandle.getVolatile() which + * are unsuitable for reflection because of their signature polymorphism, but their + * lambda forms call implementation methods in VarHandle subclasses that we can use. + */ + var delegating = SubstrateUtil.cast(this, Target_java_lang_invoke_DelegatingMethodHandle.class); + return delegating.getTarget().invokeBasic(args); + } ret = Util_java_lang_invoke_MethodHandle.invokeInternal(memberName, type, args); - } else { /* Interpretation mode */ + } else { + /* Interpretation mode */ Target_java_lang_invoke_LambdaForm form = internalForm(); Object[] interpreterArguments = new Object[args.length + 1]; interpreterArguments[0] = this; @@ -257,6 +272,12 @@ void ensureInitialized() { } } +@TargetClass(className = "java.lang.invoke.DelegatingMethodHandle") +final class Target_java_lang_invoke_DelegatingMethodHandle { + @Alias + native Target_java_lang_invoke_MethodHandle getTarget(); +} + @TargetClass(className = "java.lang.invoke.MethodHandleImpl") final class Target_java_lang_invoke_MethodHandleImpl { } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java index 4ddba67980c8..e50234f4ac46 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java @@ -287,7 +287,7 @@ private static void forceAccess(AccessibleObject target) { @SuppressWarnings("unused") public static Target_java_lang_invoke_MemberName resolve(Target_java_lang_invoke_MemberName self, Class caller, boolean speculativeResolve) throws LinkageError, ClassNotFoundException { - if (self.reflectAccess != null) { + if (self.reflectAccess != null || self.intrinsic != null) { return self; } Class declaringClass = self.getDeclaringClass(); From 136bc557de268aea8be24d36ae86c15bae470994 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Mon, 3 Jul 2023 09:20:36 +0200 Subject: [PATCH 12/33] Avoid class initialization for DirectMethodHandle altogether if possible. --- .../nodes/MethodHandleWithExceptionNode.java | 2 +- .../Target_java_lang_invoke_MethodHandle.java | 1 + .../hosted/snippets/ReflectionPlugins.java | 43 ++++++++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleWithExceptionNode.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleWithExceptionNode.java index f26d1f3862e0..d371bde9b6cb 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleWithExceptionNode.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/nodes/MethodHandleWithExceptionNode.java @@ -77,7 +77,7 @@ public WithExceptionNode trySimplify(MethodHandleAccessProvider methodHandleAcce } assert invoke.graph() == null; invoke.setNodeSourcePosition(getNodeSourcePosition()); - graph().addOrUniqueWithInputs(invoke); + invoke = graph().addOrUniqueWithInputs(invoke); invoke.setStateAfter(stateAfter()); graph().replaceWithExceptionSplit(this, invoke); return invoke; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java index b49b1f982c1c..8e03bb1fe6f2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java @@ -268,6 +268,7 @@ final class Target_java_lang_invoke_DirectMethodHandle { @Substitute void ensureInitialized() { + // This method is also intrinsified to avoid initialization altogether whenever possible. EnsureClassInitializedNode.ensureClassInitialized(member.getDeclaringClass()); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java index 89ddd71d8d04..92a1812b9aa0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java @@ -49,6 +49,7 @@ import java.util.stream.Stream; import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; +import org.graalvm.compiler.debug.GraalError; import org.graalvm.compiler.nodes.ConstantNode; import org.graalvm.compiler.nodes.ValueNode; import org.graalvm.compiler.nodes.graphbuilderconf.ClassInitializationPlugin; @@ -87,6 +88,7 @@ import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @@ -210,13 +212,50 @@ private void registerMethodHandlesPlugins(InvocationPlugins plugins) { registerConditionalFoldInvocationPlugins(plugins); - Registration r = new Registration(plugins, MethodHandles.class); - r.register(new RequiredInlineOnlyInvocationPlugin("lookup") { + Registration mh = new Registration(plugins, MethodHandles.class); + mh.register(new RequiredInlineOnlyInvocationPlugin("lookup") { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver) { return processMethodHandlesLookup(b, targetMethod); } }); + + Registration dmh = new Registration(plugins, "java.lang.invoke.DirectMethodHandle"); + dmh.register(new RequiredInvocationPlugin("ensureInitialized", Receiver.class) { + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver) { + JavaConstant constReceiver = receiver.get().asJavaConstant(); + if (constReceiver == null || constReceiver.isNull()) { + return false; + } + ResolvedJavaField memberField = findField(targetMethod.getDeclaringClass(), "member"); // final + JavaConstant member = b.getConstantReflection().readFieldValue(memberField, constReceiver); + if (member == null || member.isNull()) { + return false; + } + /* + * The clazz field of MemberName qualifies as stable except when an object is cloned + * and the new object's field is nulled. We should not observe it in that state. + */ + ResolvedJavaField clazzField = findField(memberField.getType().resolve(memberField.getDeclaringClass()), "clazz"); + JavaConstant clazz = b.getConstantReflection().readFieldValue(clazzField, member); + ResolvedJavaType type = b.getConstantReflection().asJavaType(clazz); + if (type == null) { + return false; + } + classInitializationPlugin.apply(b, type, () -> null, null); + return true; + } + }); + } + + private static ResolvedJavaField findField(ResolvedJavaType type, String name) { + for (ResolvedJavaField field : type.getInstanceFields(false)) { + if (field.getName().equals(name)) { + return field; + } + } + throw GraalError.shouldNotReachHere("Required field " + name + " not found in " + type); // ExcludeFromJacocoGeneratedReport } /** From c0a593395b042ffade4b1b6117aacd65787e0b68 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 4 Jul 2023 11:43:25 +0200 Subject: [PATCH 13/33] If -H:-InlineBeforeAnalysis, fall back to IntrinsifyMethodHandlesInvocationPlugin. --- .../src/com/oracle/svm/hosted/NativeImageGenerator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 1698fecf2aad..dea6dcbc9143 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -156,6 +156,7 @@ import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.graal.pointsto.meta.PointsToAnalysisFactory; +import com.oracle.graal.pointsto.phases.InlineBeforeAnalysis; import com.oracle.graal.pointsto.reports.AnalysisReporter; import com.oracle.graal.pointsto.reports.ReportUtils; import com.oracle.graal.pointsto.typestate.DefaultAnalysisPolicy; @@ -1306,7 +1307,7 @@ public static void registerGraphBuilderPlugins(FeatureHandler featureHandler, Ru SubstrateReplacements replacements = (SubstrateReplacements) providers.getReplacements(); plugins.appendInlineInvokePlugin(replacements); - if (SubstrateOptions.parseOnce()) { + if (SubstrateOptions.parseOnce() && InlineBeforeAnalysis.Options.InlineBeforeAnalysis.getValue(aUniverse.getBigbang().getOptions())) { if (reason.duringAnalysis()) { plugins.appendNodePlugin(new MethodHandleWithExceptionPlugin(providers.getConstantReflection().getMethodHandleAccess(), false)); } From 9f0463331a1a5f905c3906c18721a97b342f41d7 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 4 Jul 2023 21:17:37 +0200 Subject: [PATCH 14/33] Match now visible method handle frames in debugging tests. --- substratevm/mx.substratevm/testhello.py | 28 ++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/mx.substratevm/testhello.py index f537c2f96303..bce0f576a49a 100644 --- a/substratevm/mx.substratevm/testhello.py +++ b/substratevm/mx.substratevm/testhello.py @@ -124,12 +124,14 @@ def test(): # expect "#1 0x[0-9a-f]+ in com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_.* at [a-z/]+/JavaMainWrapper.java:[0-9]+" exec_string = execute("backtrace") stacktraceRegex = [r"#0%shello\.Hello::main%s %s at hello/Hello\.java:%d"%(spaces_pattern, param_types_pattern, arg_values_pattern, main_start), - r"#1%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#2%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), - r"#3%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), - r"#4%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#5%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#6%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern) + r"#1%s(%s in )?java\.lang\.invoke\.LambdaForm\$DMH/0x%s::invokeStatic(Init)?%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), + r"#2%s(%s in )?java\.lang\.invoke\.LambdaForm\$MH/0x%s::invokeExact_MT%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), + r"#3%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#4%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), + r"#5%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), + r"#6%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#7%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#8%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern) ] if musl: # musl has a different entry point - drop the last two frames @@ -369,12 +371,14 @@ def test(): exec_string = execute("backtrace") stacktraceRegex = [r"#0%shello\.Hello\$Greeter::greeter%s %s at hello/Hello\.java:38"%(spaces_pattern, param_types_pattern, arg_values_pattern), r"#1%s%s in hello\.Hello::main%s %s at hello/Hello\.java:%d"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, main_start), - r"#2%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#3%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), - r"#4%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), - r"#5%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#6%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#7%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern) + r"#2%s(%s in )?java\.lang\.invoke\.LambdaForm\$DMH/0x%s::invokeStatic(Init)?%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), + r"#3%s(%s in )?java\.lang\.invoke\.LambdaForm\$MH/0x%s::invokeExact_MT%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), + r"#4%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#5%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), + r"#6%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), + r"#7%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#8%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#9%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern) ] if musl: # musl has a different entry point - drop the last two frames From 87b561017589bb31ecbebf38eb79d2383c4e5c46 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Fri, 7 Jul 2023 09:48:01 +0200 Subject: [PATCH 15/33] Style fix. --- .../graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java index 8cf4efcbb706..1253e178a3ee 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java @@ -269,7 +269,7 @@ protected LoopScope handleMethodHandle(MethodScope s, LoopScope loopScope, Invok InvokeWithExceptionNode invoke = (InvokeWithExceptionNode) replacement; registerNode(loopScope, invokableData.orderId, invoke, true, false); InvokeData invokeData = new InvokeData(invoke, invokableData.contextType, invokableData.orderId, -1, intrinsifiedMethodHandle, invokableData.stateAfterOrderId, - invokableData.nextOrderId, invokableData.exceptionOrderId, invokableData.exceptionStateOrderId, invokableData.exceptionNextOrderId); + invokableData.nextOrderId, invokableData.exceptionOrderId, invokableData.exceptionStateOrderId, invokableData.exceptionNextOrderId); CallTargetNode callTarget; if (invoke.callTarget() instanceof ResolvedMethodHandleCallTargetNode t) { From 75bdc1000cbd15e88d802586017fe02c219dd4f2 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 11 Jul 2023 12:40:32 +0200 Subject: [PATCH 16/33] Omit information about inlined generated code for method handle invocations for PGO robustness. --- .../compiler/replacements/PEGraphDecoder.java | 16 ++++++++++++ substratevm/mx.substratevm/testhello.py | 26 +++++++++---------- .../InlineBeforeAnalysisGraphDecoder.java | 5 ++++ .../phases/InlineBeforeAnalysisPolicy.java | 4 +++ .../InlineBeforeAnalysisPolicyImpl.java | 5 ++++ .../InlineBeforeAnalysisPolicyUtils.java | 16 ++++++++++++ 6 files changed, 58 insertions(+), 14 deletions(-) diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java index 5a4e5606898f..dbb941ff6be3 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java @@ -267,6 +267,9 @@ private NodeSourcePosition resolveCallerBytecodePosition() { if (invokePosition == null) { return null; } + if (invokePosition.getCaller() != null && shouldOmitIntermediateMethodInStates(invokePosition.getMethod())) { + invokePosition = invokePosition.getCaller(); + } callerBytecodePosition = invokePosition; } return callerBytecodePosition; @@ -1665,10 +1668,23 @@ protected void ensureOuterStateDecoded(PEMethodScope methodScope) { ensureOuterStateDecoded(methodScope.caller); outerState.setOuterFrameState(methodScope.caller.outerState); } + if (outerState.outerFrameState() != null && shouldOmitIntermediateMethodInStates(outerState.getMethod())) { + outerState = outerState.outerFrameState(); + } methodScope.outerState = outerState; } } + /** + * Determines whether to omit an intermediate method (a method other than the root method or a + * leaf callee) from {@link FrameState} or {@link NodeSourcePosition} information. When used to + * discard intermediate methods of generated code with non-deterministic names, for example, + * this can improve matching of profile-guided optimization information between executions. + */ + protected boolean shouldOmitIntermediateMethodInStates(@SuppressWarnings("unused") ResolvedJavaMethod method) { + return false; + } + protected void ensureStateAfterDecoded(PEMethodScope methodScope) { if (methodScope.invokeData.invoke.stateAfter() == null) { methodScope.invokeData.invoke.setStateAfter((FrameState) ensureNodeCreated(methodScope.caller, methodScope.callerLoopScope, methodScope.invokeData.stateAfterOrderId)); diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/mx.substratevm/testhello.py index bce0f576a49a..a7b0ecce0c71 100644 --- a/substratevm/mx.substratevm/testhello.py +++ b/substratevm/mx.substratevm/testhello.py @@ -125,13 +125,12 @@ def test(): exec_string = execute("backtrace") stacktraceRegex = [r"#0%shello\.Hello::main%s %s at hello/Hello\.java:%d"%(spaces_pattern, param_types_pattern, arg_values_pattern, main_start), r"#1%s(%s in )?java\.lang\.invoke\.LambdaForm\$DMH/0x%s::invokeStatic(Init)?%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), - r"#2%s(%s in )?java\.lang\.invoke\.LambdaForm\$MH/0x%s::invokeExact_MT%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), - r"#3%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#4%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), - r"#5%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), - r"#6%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#7%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#8%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern) + r"#2%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#3%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), + r"#4%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), + r"#5%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#6%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#7%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern) ] if musl: # musl has a different entry point - drop the last two frames @@ -372,13 +371,12 @@ def test(): stacktraceRegex = [r"#0%shello\.Hello\$Greeter::greeter%s %s at hello/Hello\.java:38"%(spaces_pattern, param_types_pattern, arg_values_pattern), r"#1%s%s in hello\.Hello::main%s %s at hello/Hello\.java:%d"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, main_start), r"#2%s(%s in )?java\.lang\.invoke\.LambdaForm\$DMH/0x%s::invokeStatic(Init)?%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), - r"#3%s(%s in )?java\.lang\.invoke\.LambdaForm\$MH/0x%s::invokeExact_MT%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), - r"#4%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#5%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), - r"#6%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), - r"#7%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#8%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), - r"#9%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern) + r"#3%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#4%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), + r"#5%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), + r"#6%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#7%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), + r"#8%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern) ] if musl: # musl has a different entry point - drop the last two frames diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java index 1253e178a3ee..c41039296249 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java @@ -158,6 +158,11 @@ protected final Node canonicalizeFixedNode(MethodScope methodScope, LoopScope lo return canonical; } + @Override + protected boolean shouldOmitIntermediateMethodInStates(ResolvedJavaMethod method) { + return policy.shouldOmitIntermediateMethodInState(method); + } + @SuppressWarnings("unused") protected Node doCanonicalizeFixedNode(InlineBeforeAnalysisMethodScope methodScope, LoopScope loopScope, Node node) { return node; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java index 98795e1f8086..714f59f7445b 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java @@ -108,6 +108,10 @@ protected InlineBeforeAnalysisPolicy(NodePlugin[] nodePlugins) { protected abstract AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle); + protected boolean shouldOmitIntermediateMethodInState(ResolvedJavaMethod method) { + return false; + } + public static final InlineBeforeAnalysisPolicy NO_INLINING = new InlineBeforeAnalysisPolicy(new NodePlugin[0]) { @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java index 5c6dfa3d7bdc..6f5557ca8578 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java @@ -111,6 +111,11 @@ protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, Analysi metaAccess, method, intrinsifiedMethodHandle); } + @Override + protected boolean shouldOmitIntermediateMethodInState(ResolvedJavaMethod method) { + return inliningUtils.shouldOmitIntermediateMethodInState(method); + } + @Override protected FixedWithNextNode processInvokeArgs(ResolvedJavaMethod targetMethod, FixedWithNextNode insertionPoint, ValueNode[] arguments) { // No action is needed diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java index 98291a3801db..6b1ed1775cc4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.hosted.phases; +import java.lang.annotation.Annotation; import java.lang.invoke.MethodHandle; import java.util.Arrays; import java.util.Map; @@ -92,6 +93,7 @@ public static class Options { private AnalysisType methodHandleType; private AnalysisType varHandleGuardsType; + private Class compiledLambdaFormAnnotation; public static boolean inliningAllowed(SVMHost hostVM, GraphBuilderContext b, ResolvedJavaMethod method) { AnalysisMethod caller = (AnalysisMethod) b.getMethod(); @@ -453,4 +455,18 @@ private static boolean inlineForMethodHandleIntrinsification(ResolvedJavaMethod } return false; } + + /** + * Discard information on inlined calls to generated classes of LambdaForms, which are not + * assigned names that are stable between executions and would cause mismatches in collected + * profile-guided optimization data which prevent optimizations. + */ + protected boolean shouldOmitIntermediateMethodInState(ResolvedJavaMethod method) { + if (compiledLambdaFormAnnotation == null) { + @SuppressWarnings("unchecked") + Class annotation = (Class) ReflectionUtil.lookupClass(false, "java.lang.invoke.LambdaForm$Compiled"); + compiledLambdaFormAnnotation = annotation; + } + return method.isAnnotationPresent(compiledLambdaFormAnnotation); + } } From 88311adfda77f77785bfe0509f520b3c8ee1cbaf Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 12 Jul 2023 13:46:15 +0200 Subject: [PATCH 17/33] Restrict inlining again once calling out of the method handle apparatus. --- .../InlineBeforeAnalysisPolicyUtils.java | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java index 6b1ed1775cc4..2d49207f3d24 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java @@ -82,7 +82,7 @@ public static class Options { @Option(help = "Maximum number of invokes for method inlined before static analysis")// public static final HostedOptionKey InlineBeforeAnalysisAllowedInvokes = new HostedOptionKey<>(1); - @Option(help = "Maximum number of invokes for method inlined before static analysis")// + @Option(help = "Maximum call depth for method inlined before static analysis")// public static final HostedOptionKey InlineBeforeAnalysisAllowedDepth = new HostedOptionKey<>(20); } @@ -178,15 +178,16 @@ public String toString() { } static final class AccumulativeCounters { - static AccumulativeCounters createRoot() { + static AccumulativeCounters create(AccumulativeCounters outer) { + int maxDepth = (outer != null) ? outer.maxInliningDepth : Options.InlineBeforeAnalysisAllowedDepth.getValue(); return new AccumulativeCounters(Options.InlineBeforeAnalysisAllowedNodes.getValue(), Options.InlineBeforeAnalysisAllowedInvokes.getValue(), - Options.InlineBeforeAnalysisAllowedDepth.getValue(), + maxDepth, false); } - static AccumulativeCounters createForMethodHandleIntrinsification(int startDepth) { - return new AccumulativeCounters(100, 20, startDepth + 20, true); + static AccumulativeCounters createForMethodHandleIntrinsification(AccumulativeCounters outer) { + return new AccumulativeCounters(100, 20, outer.maxInliningDepth, true); } int maxNodes; @@ -219,7 +220,7 @@ public AccumulativeInlineScope createAccumulativeInlineScope(AccumulativeInlineS * point of view. */ depth = 1; - accumulativeCounters = AccumulativeCounters.createRoot(); + accumulativeCounters = AccumulativeCounters.create(null); } else if (!outer.accumulativeCounters.inMethodHandleIntrinsification && (intrinsifiedMethodHandle || isMethodHandleIntrinsificationRoot(metaAccess, method))) { /* @@ -228,7 +229,21 @@ public AccumulativeInlineScope createAccumulativeInlineScope(AccumulativeInlineS * handle intrinsification context. */ depth = outer.inliningDepth + 1; - accumulativeCounters = AccumulativeCounters.createForMethodHandleIntrinsification(depth); + accumulativeCounters = AccumulativeCounters.createForMethodHandleIntrinsification(outer.accumulativeCounters); + + } else if (outer.accumulativeCounters.inMethodHandleIntrinsification && !inlineForMethodHandleIntrinsification(method)) { + /* + * Method which is invoked in method handle intrinsification but which is not part of + * the method handle apparatus, for example, the target method of a direct method + * handle: create a scope with the restrictive regular limits (although it is an + * additional scope, therefore still permitting more nodes in total) + * + * This assumes that the regular limits are strict enough to prevent excessive inlining + * triggered by method handles. We could also use alternative fixed values or the option + * defaults instead of any set option values. + */ + depth = outer.inliningDepth + 1; + accumulativeCounters = AccumulativeCounters.create(outer.accumulativeCounters); } else { /* Nested inlining (potentially during method handle intrinsification). */ @@ -281,8 +296,6 @@ public final class AccumulativeInlineScope extends InlineBeforeAnalysisPolicy.Ab int numNodes = 0; int numInvokes = 0; - Boolean lenientForMethodHandleIntrinsic = null; // lazily initialized - AccumulativeInlineScope(AccumulativeCounters accumulativeCounters, int inliningDepth) { super(inliningDepth); this.accumulativeCounters = accumulativeCounters; @@ -298,7 +311,7 @@ public boolean allowAbort() { public void commitCalleeScope(InlineBeforeAnalysisPolicy.AbstractPolicyScope callee) { AccumulativeInlineScope calleeScope = (AccumulativeInlineScope) callee; if (accumulativeCounters != calleeScope.accumulativeCounters) { - assert !accumulativeCounters.inMethodHandleIntrinsification && calleeScope.accumulativeCounters.inMethodHandleIntrinsification; + assert accumulativeCounters.inMethodHandleIntrinsification != calleeScope.accumulativeCounters.inMethodHandleIntrinsification; // Expand limits to hold the method handle intrinsification, but not more. accumulativeCounters.maxNodes += calleeScope.numNodes; @@ -318,7 +331,7 @@ public void abortCalleeScope(InlineBeforeAnalysisPolicy.AbstractPolicyScope call accumulativeCounters.numNodes -= calleeScope.numNodes; accumulativeCounters.numInvokes -= calleeScope.numInvokes; } else { - assert !accumulativeCounters.inMethodHandleIntrinsification && calleeScope.accumulativeCounters.inMethodHandleIntrinsification; + assert accumulativeCounters.inMethodHandleIntrinsification != calleeScope.accumulativeCounters.inMethodHandleIntrinsification; } } @@ -421,13 +434,8 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met numNodes++; accumulativeCounters.numNodes++; - if (!allow && accumulativeCounters.inMethodHandleIntrinsification) { - if (lenientForMethodHandleIntrinsic == null) { - lenientForMethodHandleIntrinsic = inlineForMethodHandleIntrinsification(method); - } - allow = lenientForMethodHandleIntrinsic; - } - return allow; + // With method handle intrinsification we permit all node types to become more effective + return allow || accumulativeCounters.inMethodHandleIntrinsification; } @Override From 7a8284ae22dc04ccd68c2f20934b5289b07c718a Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Mon, 17 Jul 2023 19:47:11 +0200 Subject: [PATCH 18/33] Recompute DirectMethodHandle.{Accessor,StaticAccessor} fields. --- .../oracle/svm/core/jdk/VarHandleFeature.java | 157 +++++++++++++----- 1 file changed, 117 insertions(+), 40 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java index aadf76297688..c6c22af9797e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.jdk; +import java.lang.invoke.MethodHandle; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.HashMap; @@ -31,6 +32,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.ToLongFunction; import org.graalvm.nativeimage.ImageSingletons; @@ -50,9 +53,9 @@ import jdk.internal.misc.Unsafe; /** - * This file contains most of the code necessary for supporting VarHandle in native images. The - * actual intrinsification of VarHandle accessors happens in hosted-only code during inlining before - * analysis. + * This file contains most of the code necessary for supporting VarHandle (and DirectMethodHandle + * field accessors) in native images. The actual intrinsification of the accessors happens in + * hosted-only code during inlining before analysis. * * The VarHandle implementation in the JDK uses some invokedynamic and method handles, but also a * lot of explicit Java code (a lot of it automatically generated): The main entry point from the @@ -107,52 +110,81 @@ public void afterRegistration(AfterRegistrationAccess access) { Class.forName("java.lang.invoke.VarHandle" + typeName + "$FieldStaticReadOnly"), Class.forName("java.lang.invoke.VarHandle" + typeName + "$FieldStaticReadWrite")); } + + Class staticAccessorClass = Class.forName("java.lang.invoke.DirectMethodHandle$StaticAccessor"); + infos.put(staticAccessorClass, new VarHandleInfo(true, createOffsetFieldGetter(staticAccessorClass, "staticOffset"), + createTypeFieldGetter(staticAccessorClass, "staticBase"))); + + Class accessorClass = Class.forName("java.lang.invoke.DirectMethodHandle$Accessor"); + Function> accessorTypeGetter = obj -> ((MethodHandle) obj).type().parameterType(0); + infos.put(accessorClass, new VarHandleInfo(false, createOffsetFieldGetter(accessorClass, "fieldOffset"), accessorTypeGetter)); + } catch (ClassNotFoundException ex) { throw VMError.shouldNotReachHere(ex); } } private void buildInfo(boolean isStatic, String typeFieldName, Class readOnlyClass, Class readWriteClass) { - VarHandleInfo info = new VarHandleInfo(isStatic, ReflectionUtil.lookupField(readOnlyClass, "fieldOffset"), ReflectionUtil.lookupField(readOnlyClass, typeFieldName)); - infos.put(readOnlyClass, info); - infos.put(readWriteClass, info); + ToLongFunction offsetGetter = createOffsetFieldGetter(readOnlyClass, "fieldOffset"); + Function> typeGetter = createTypeFieldGetter(readOnlyClass, typeFieldName); + VarHandleInfo readOnlyInfo = new VarHandleInfo(isStatic, offsetGetter, typeGetter); + infos.put(readOnlyClass, readOnlyInfo); + infos.put(readWriteClass, readOnlyInfo); + } + + private static ToLongFunction createOffsetFieldGetter(Class clazz, String offsetFieldName) { + Field offsetField = ReflectionUtil.lookupField(clazz, offsetFieldName); + return obj -> { + try { + return offsetField.getLong(obj); + } catch (IllegalAccessException e) { + throw VMError.shouldNotReachHere(e); + } + }; + } + + private static Function> createTypeFieldGetter(Class clazz, String typeFieldName) { + Field typeField = ReflectionUtil.lookupField(clazz, typeFieldName); + return obj -> { + try { + return (Class) typeField.get(obj); + } catch (IllegalAccessException e) { + throw VMError.shouldNotReachHere(e); + } + }; } /** - * Find the original {@link Field} referenced by a VarHandle that accessed an instance field or - * a static field field. The VarHandle stores the field offset and the holder type. We iterate - * all fields of that type and look for the field with a matching offset. + * Find the original {@link Field} referenced by a VarHandle that accesses an instance field or + * a static field. The VarHandle stores the field offset and the holder type. We iterate all + * fields of that type and look for the field with a matching offset. */ Field findVarHandleField(Object varHandle) { - try { - VarHandleInfo info = infos.get(varHandle.getClass()); - long originalFieldOffset = info.fieldOffsetField.getLong(varHandle); - Class type = (Class) info.typeField.get(varHandle); - - for (Class cur = type; cur != null; cur = cur.getSuperclass()) { - /* Search the declared fields for a field with a matching offset. */ - for (Field field : cur.getDeclaredFields()) { - if (Modifier.isStatic(field.getModifiers()) == info.isStatic) { - long fieldOffset = info.isStatic ? Unsafe.getUnsafe().staticFieldOffset(field) : Unsafe.getUnsafe().objectFieldOffset(field); - if (fieldOffset == originalFieldOffset) { - return field; - } + VarHandleInfo info = infos.get(varHandle.getClass()); + long originalFieldOffset = info.offsetGetter.applyAsLong(varHandle); + Class type = info.typeGetter.apply(varHandle); + + for (Class cur = type; cur != null; cur = cur.getSuperclass()) { + /* Search the declared fields for a field with a matching offset. */ + for (Field field : cur.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers()) == info.isStatic) { + long fieldOffset = info.isStatic ? Unsafe.getUnsafe().staticFieldOffset(field) : Unsafe.getUnsafe().objectFieldOffset(field); + if (fieldOffset == originalFieldOffset) { + return field; } } - - if (info.isStatic) { - /* - * For instance fields, we need to search the whole class hierarchy. For static - * fields, we must only search the exact class. - */ - break; - } } - throw VMError.shouldNotReachHere("Could not find field referenced in VarHandle: " + type + ", offset = " + originalFieldOffset + ", isStatic = " + info.isStatic); - } catch (ReflectiveOperationException ex) { - throw VMError.shouldNotReachHere(ex); + if (info.isStatic) { + /* + * For instance fields, we need to search the whole class hierarchy. For static + * fields, we must only search the exact class. + */ + break; + } } + + throw VMError.shouldNotReachHere("Could not find field referenced in VarHandle: " + type + ", offset = " + originalFieldOffset + ", isStatic = " + info.isStatic); } @Override @@ -190,17 +222,17 @@ private Object processVarHandle(Object obj) { class VarHandleInfo { final boolean isStatic; - final Field fieldOffsetField; - final Field typeField; + final ToLongFunction offsetGetter; + final Function> typeGetter; - VarHandleInfo(boolean isStatic, Field fieldOffsetField, Field typeField) { + VarHandleInfo(boolean isStatic, ToLongFunction offsetGetter, Function> typeGetter) { this.isStatic = isStatic; - this.fieldOffsetField = fieldOffsetField; - this.typeField = typeField; + this.offsetGetter = offsetGetter; + this.typeGetter = typeGetter; } } -class VarHandleFieldOffsetComputer implements FieldValueTransformerWithAvailability { +class VarHandleFieldOffsetIntComputer implements FieldValueTransformerWithAvailability { @Override public ValueAvailability valueAvailability() { return ValueAvailability.AfterAnalysis; @@ -213,7 +245,15 @@ public Object transform(Object receiver, Object originalValue) { if (offset <= 0) { throw VMError.shouldNotReachHere("Field is not marked as unsafe accessed: " + field); } - return Long.valueOf(offset); + return offset; + } +} + +class VarHandleFieldOffsetComputer extends VarHandleFieldOffsetIntComputer { + @Override + public Object transform(Object receiver, Object originalValue) { + Object offset = super.transform(receiver, originalValue); + return Long.valueOf((Integer) offset); } } @@ -463,3 +503,40 @@ final class Target_java_lang_invoke_VarHandleReferences_FieldStaticReadOnly { @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = VarHandleFieldOffsetComputer.class) // long fieldOffset; } + +/* + * DirectMethodHandle$Accessor and DirectMethodHandle$StaticAccessor predate VarHandle, but have a + * similar purpose and must be handled similarly. + */ + +@TargetClass(className = "java.lang.invoke.DirectMethodHandle", innerClass = "Accessor") +final class Target_java_lang_invoke_DirectMethodHandle_Accessor { + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = VarHandleFieldOffsetIntComputer.class) // + int fieldOffset; +} + +@TargetClass(className = "java.lang.invoke.DirectMethodHandle", innerClass = "StaticAccessor") +final class Target_java_lang_invoke_DirectMethodHandle_StaticAccessor { + @Alias // + Class fieldType; + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = StaticAccessorFieldStaticBaseComputer.class) // + Object staticBase; + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = VarHandleFieldOffsetComputer.class) // + long staticOffset; +} + +class StaticAccessorFieldStaticBaseComputer implements FieldValueTransformerWithAvailability { + @Override + public FieldValueTransformerWithAvailability.ValueAvailability valueAvailability() { + return FieldValueTransformerWithAvailability.ValueAvailability.AfterAnalysis; + } + + @Override + public Object transform(Object receiver, Object originalValue) { + Field field = ImageSingletons.lookup(VarHandleFeature.class).findVarHandleField(receiver); + if (field.getType().isPrimitive()) { + return StaticFieldsSupport.getStaticPrimitiveFields(); + } + return StaticFieldsSupport.getStaticObjectFields(); + } +} From c66732919574b7b25b92e6509aff0b8d1c6a79fa Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Mon, 17 Jul 2023 21:08:03 +0200 Subject: [PATCH 19/33] Trigger method handle inlining only with constant method handle arguments. --- .../InlineBeforeAnalysisGraphDecoder.java | 6 +++- .../phases/InlineBeforeAnalysisPolicy.java | 6 ++-- .../ParseOnceRuntimeCompilationFeature.java | 7 +++-- .../SimulateClassInitializerPolicy.java | 3 +- .../InlineBeforeAnalysisPolicyImpl.java | 6 ++-- .../InlineBeforeAnalysisPolicyUtils.java | 30 ++++++++++++++----- 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java index c41039296249..1ff40bde324d 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java @@ -85,7 +85,11 @@ public class InlineBeforeAnalysisMethodScope extends PEMethodScope { graph.getDebug().logv(" ".repeat(inliningDepth) + "createRootScope for " + method.format("%H.%n(%p)") + ": " + policyScope); } } else { - policyScope = policy.openCalleeScope(cast(caller).policyScope, bb.getMetaAccess(), method, invokeData.intrinsifiedMethodHandle); + boolean[] constArgsWithReceiver = new boolean[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + constArgsWithReceiver[i] = arguments[i].isConstant(); + } + policyScope = policy.openCalleeScope(cast(caller).policyScope, bb.getMetaAccess(), method, constArgsWithReceiver, invokeData.intrinsifiedMethodHandle); if (graph.getDebug().isLogEnabled()) { graph.getDebug().logv(" ".repeat(inliningDepth) + "openCalleeScope for " + method.format("%H.%n(%p)") + ": " + policyScope); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java index 714f59f7445b..8469a4b81ff1 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java @@ -106,7 +106,8 @@ protected InlineBeforeAnalysisPolicy(NodePlugin[] nodePlugins) { protected abstract AbstractPolicyScope createRootScope(); - protected abstract AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle); + protected abstract AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, + ResolvedJavaMethod method, boolean[] constArgsWithReceiver, boolean intrinsifiedMethodHandle); protected boolean shouldOmitIntermediateMethodInState(ResolvedJavaMethod method) { return false; @@ -150,7 +151,8 @@ protected AbstractPolicyScope createRootScope() { } @Override - protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle) { + protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, + ResolvedJavaMethod method, boolean[] constArgsWithReceiver, boolean intrinsifiedMethodHandle) { throw AnalysisError.shouldNotReachHere("NO_INLINING policy should not try to inline"); } }; diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceRuntimeCompilationFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceRuntimeCompilationFeature.java index cddd653e8d11..af349c92988b 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceRuntimeCompilationFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/ParseOnceRuntimeCompilationFeature.java @@ -1033,13 +1033,14 @@ protected AbstractPolicyScope createRootScope() { } @Override - protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle) { + protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, + ResolvedJavaMethod method, boolean[] constArgsWithReceiver, boolean intrinsifiedMethodHandle) { if (outer instanceof AccumulativeInlineScope accOuter) { /* * once the accumulative policy is activated, then we cannot return to the trivial * policy */ - return inliningUtils.createAccumulativeInlineScope(accOuter, metaAccess, method, intrinsifiedMethodHandle); + return inliningUtils.createAccumulativeInlineScope(accOuter, metaAccess, method, constArgsWithReceiver, intrinsifiedMethodHandle); } assert outer == null || outer instanceof AlwaysInlineScope : "unexpected outer scope: " + outer; @@ -1051,7 +1052,7 @@ protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, Analysi return new AlwaysInlineScope(inliningDepth); } else { // start with a new accumulative inline scope - return inliningUtils.createAccumulativeInlineScope(null, metaAccess, method, intrinsifiedMethodHandle); + return inliningUtils.createAccumulativeInlineScope(null, metaAccess, method, constArgsWithReceiver, intrinsifiedMethodHandle); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java index bb6fc79fa5ec..a43c9f28d0d4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java @@ -159,7 +159,8 @@ protected AbstractPolicyScope createRootScope() { } @Override - protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope o, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle) { + protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope o, AnalysisMetaAccess metaAccess, + ResolvedJavaMethod method, boolean[] constArgsWithReceiver, boolean intrinsifiedMethodHandle) { var outer = (SimulateClassInitializerInlineScope) o; return new SimulateClassInitializerInlineScope(outer.accumulativeCounters, outer.inliningDepth + 1); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java index 6f5557ca8578..b132838fe87e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyImpl.java @@ -106,9 +106,9 @@ protected AbstractPolicyScope createRootScope() { } @Override - protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle) { - return inliningUtils.createAccumulativeInlineScope((InlineBeforeAnalysisPolicyUtils.AccumulativeInlineScope) outer, - metaAccess, method, intrinsifiedMethodHandle); + protected AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, + ResolvedJavaMethod method, boolean[] constArgsWithReceiver, boolean intrinsifiedMethodHandle) { + return inliningUtils.createAccumulativeInlineScope((InlineBeforeAnalysisPolicyUtils.AccumulativeInlineScope) outer, metaAccess, method, constArgsWithReceiver, intrinsifiedMethodHandle); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java index 2d49207f3d24..c4d5c0aebb06 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java @@ -26,7 +26,6 @@ import java.lang.annotation.Annotation; import java.lang.invoke.MethodHandle; -import java.util.Arrays; import java.util.Map; import java.util.Set; @@ -211,7 +210,8 @@ private AccumulativeCounters(int maxNodes, int maxInvokes, int maxInliningDepth, * has exceeded a specified count, or an illegal node is inlined, then the process will be * aborted. */ - public AccumulativeInlineScope createAccumulativeInlineScope(AccumulativeInlineScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean intrinsifiedMethodHandle) { + public AccumulativeInlineScope createAccumulativeInlineScope(AccumulativeInlineScope outer, AnalysisMetaAccess metaAccess, + ResolvedJavaMethod method, boolean[] constArgsWithReceiver, boolean intrinsifiedMethodHandle) { AccumulativeCounters accumulativeCounters; int depth; if (outer == null) { @@ -222,7 +222,7 @@ public AccumulativeInlineScope createAccumulativeInlineScope(AccumulativeInlineS depth = 1; accumulativeCounters = AccumulativeCounters.create(null); - } else if (!outer.accumulativeCounters.inMethodHandleIntrinsification && (intrinsifiedMethodHandle || isMethodHandleIntrinsificationRoot(metaAccess, method))) { + } else if (!outer.accumulativeCounters.inMethodHandleIntrinsification && (intrinsifiedMethodHandle || isMethodHandleIntrinsificationRoot(metaAccess, method, constArgsWithReceiver))) { /* * Method handle intrinsification root: create counters with relaxed limits and permit * more types of nodes, but not recursively, i.e., not if we are already in a method @@ -253,15 +253,31 @@ public AccumulativeInlineScope createAccumulativeInlineScope(AccumulativeInlineS return new AccumulativeInlineScope(accumulativeCounters, depth); } - private boolean isMethodHandleIntrinsificationRoot(AnalysisMetaAccess metaAccess, ResolvedJavaMethod method) { - return (isVarHandleMethod(metaAccess, method) || hasMethodHandleParameter(metaAccess, method)) && !isIgnoredMethodHandleMethod(method); + private boolean isMethodHandleIntrinsificationRoot(AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean[] constArgsWithReceiver) { + return (isVarHandleMethod(metaAccess, method) || hasConstantMethodHandleParameter(metaAccess, method, constArgsWithReceiver)) && !isIgnoredMethodHandleMethod(method); } - private boolean hasMethodHandleParameter(AnalysisMetaAccess metaAccess, ResolvedJavaMethod method) { + private boolean hasConstantMethodHandleParameter(AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean[] constArgsWithReceiver) { if (methodHandleType == null) { methodHandleType = metaAccess.lookupJavaType(MethodHandle.class); } - return Arrays.stream(method.toParameterTypes()).anyMatch(type -> methodHandleType.isAssignableFrom((ResolvedJavaType) type)); + for (int i = 0; i < constArgsWithReceiver.length; i++) { + if (constArgsWithReceiver[i] && methodHandleType.isAssignableFrom(getParameterType(method, i))) { + return true; + } + } + return false; + } + + private static ResolvedJavaType getParameterType(ResolvedJavaMethod method, int index) { + int i = index; + if (!method.isStatic()) { + if (i == 0) { // receiver + return method.getDeclaringClass(); + } + i--; + } + return (ResolvedJavaType) method.getSignature().getParameterType(i, null); } /** From fc263f24a64897fb62da399de57f3c10ce9cec26 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 18 Jul 2023 10:00:47 +0200 Subject: [PATCH 20/33] Minor changes. --- .../nodes/virtual/CommitAllocationNode.java | 4 +-- .../methodhandles/MethodHandleFeature.java | 8 +++--- .../InlineBeforeAnalysisPolicyUtils.java | 28 ++++++++++--------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/virtual/CommitAllocationNode.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/virtual/CommitAllocationNode.java index 3f05fdb7427f..5d2013fd235f 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/virtual/CommitAllocationNode.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/nodes/virtual/CommitAllocationNode.java @@ -123,7 +123,7 @@ public void lower(LoweringTool tool) { @Override public LocationIdentity getKilledLocationIdentity() { - if (locks == null) { + if (locks == null) { // possible if inspected during initialization in GraphDecoder return null; } if (locks.isEmpty()) { @@ -166,7 +166,7 @@ public void virtualize(VirtualizerTool tool) { @Override public Map getDebugProperties(Map map) { Map properties = super.getDebugProperties(map); - if (virtualObjects == null) { + if (virtualObjects == null) { // possible if inspected during initialization in GraphDecoder return properties; } int valuePos = 0; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java index 89c6bf0b2fc0..c3dc04690f1c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java @@ -346,8 +346,8 @@ private static void registerVarHandleMethodsForReflection(FeatureAccess access, private Object registerSeenObject(Object obj) { if (!BuildPhaseProvider.isAnalysisFinished()) { - if (obj instanceof MethodType) { - registerMethodType(obj); + if (obj instanceof MethodType mt) { + registerMethodType(mt); } else { registerMethodHandleRecurse(obj); } @@ -355,9 +355,9 @@ private Object registerSeenObject(Object obj) { return obj; } - private void registerMethodType(Object obj) { + private void registerMethodType(MethodType methodType) { try { - concurrentWeakInternSetAdd.invoke(runtimeMethodTypeInternTable, obj); + concurrentWeakInternSetAdd.invoke(runtimeMethodTypeInternTable, methodType); } catch (ReflectiveOperationException e) { throw VMError.shouldNotReachHere(e); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java index c4d5c0aebb06..92885c56cada 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java @@ -26,6 +26,7 @@ import java.lang.annotation.Annotation; import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; import java.util.Map; import java.util.Set; @@ -57,6 +58,7 @@ import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; import org.graalvm.nativeimage.AnnotationAccess; +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; @@ -85,14 +87,19 @@ public static class Options { public static final HostedOptionKey InlineBeforeAnalysisAllowedDepth = new HostedOptionKey<>(20); } - private static final Map> IGNORED_METHOD_HANDLE_METHODS = Map.of( - "java.lang.invoke.MethodHandle", Set.of("bindTo"), - "java.lang.invoke.MethodHandles", Set.of("dropArguments", "filterReturnValue", "foldArguments", "insertArguments"), - "java.lang.invoke.Invokers", Set.of("spreadInvoker")); + @SuppressWarnings("unchecked") // + private static final Class COMPILED_LAMBDA_FORM_ANNOTATION = // + (Class) ReflectionUtil.lookupClass(false, "java.lang.invoke.LambdaForm$Compiled"); + + private static final Class INVOKERS_CLASS = ReflectionUtil.lookupClass(false, "java.lang.invoke.Invokers"); + + private static final Map, Set> IGNORED_METHOD_HANDLE_METHODS = Map.of( + MethodHandle.class, Set.of("bindTo"), + MethodHandles.class, Set.of("dropArguments", "filterReturnValue", "foldArguments", "insertArguments"), + INVOKERS_CLASS, Set.of("spreadInvoker")); private AnalysisType methodHandleType; private AnalysisType varHandleGuardsType; - private Class compiledLambdaFormAnnotation; public static boolean inliningAllowed(SVMHost hostVM, GraphBuilderContext b, ResolvedJavaMethod method) { AnalysisMethod caller = (AnalysisMethod) b.getMethod(); @@ -296,8 +303,8 @@ private boolean isVarHandleMethod(AnalysisMetaAccess metaAccess, ResolvedJavaMet } private static boolean isIgnoredMethodHandleMethod(ResolvedJavaMethod method) { - String className = method.getDeclaringClass().toJavaName(true); - Set ignoredMethods = IGNORED_METHOD_HANDLE_METHODS.get(className); + Class declaringClass = OriginalClassProvider.getJavaClass(method.getDeclaringClass()); + Set ignoredMethods = IGNORED_METHOD_HANDLE_METHODS.get(declaringClass); return ignoredMethods != null && ignoredMethods.contains(method.getName()); } @@ -486,11 +493,6 @@ private static boolean inlineForMethodHandleIntrinsification(ResolvedJavaMethod * profile-guided optimization data which prevent optimizations. */ protected boolean shouldOmitIntermediateMethodInState(ResolvedJavaMethod method) { - if (compiledLambdaFormAnnotation == null) { - @SuppressWarnings("unchecked") - Class annotation = (Class) ReflectionUtil.lookupClass(false, "java.lang.invoke.LambdaForm$Compiled"); - compiledLambdaFormAnnotation = annotation; - } - return method.isAnnotationPresent(compiledLambdaFormAnnotation); + return method.isAnnotationPresent(COMPILED_LAMBDA_FORM_ANNOTATION); } } From e151bf936e6eb12aae36c7980ba4b0e6525d8049 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 18 Jul 2023 21:11:21 +0200 Subject: [PATCH 21/33] Specifically fold DirectMethodHandle$StaticAccessor usages during inlining before analysis. --- .../com/oracle/graal/pointsto/api/HostVM.java | 11 -- .../InlineBeforeAnalysisGraphDecoder.java | 102 ++++++++++++------ .../src/com/oracle/svm/hosted/SVMHost.java | 10 -- 3 files changed, 68 insertions(+), 55 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java index 73dfcd7394a7..1def9076770e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java @@ -391,15 +391,4 @@ public Function getStrengthenGraphsToTargetFunct public FieldValueComputer createFieldValueComputer(@SuppressWarnings("unused") AnalysisField field) { return null; } - - /** - * Returns the "naked" original type in the hosting VM that corresponds to the passed - * {@link AnalysisType} without any injected or changed members or attributes. - * - * In most cases, {@link AnalysisType#getWrapped} or {@link AnalysisType#getWrappedWithResolve} - * are better alternatives to this method. - */ - public ResolvedJavaType getOriginalHostType(AnalysisType type) { - return type.getWrapped(); - } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java index 1ff40bde324d..312ff5fcaea0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java @@ -24,6 +24,7 @@ */ package com.oracle.graal.pointsto.phases; +import java.lang.reflect.Field; import java.util.ArrayDeque; import java.util.Deque; import java.util.concurrent.ConcurrentHashMap; @@ -40,14 +41,16 @@ import org.graalvm.compiler.nodes.FixedNode; import org.graalvm.compiler.nodes.FixedWithNextNode; import org.graalvm.compiler.nodes.InvokeWithExceptionNode; +import org.graalvm.compiler.nodes.LogicConstantNode; import org.graalvm.compiler.nodes.StructuredGraph; import org.graalvm.compiler.nodes.ValueNode; +import org.graalvm.compiler.nodes.calc.IsNullNode; import org.graalvm.compiler.nodes.extended.UnsafeAccessNode; import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin; import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin; import org.graalvm.compiler.nodes.graphbuilderconf.LoopExplosionPlugin; +import org.graalvm.compiler.nodes.java.LoadFieldNode; import org.graalvm.compiler.nodes.java.MethodCallTargetNode; -import org.graalvm.compiler.nodes.type.StampTool; import org.graalvm.compiler.nodes.util.GraphUtil; import org.graalvm.compiler.replacements.PEGraphDecoder; import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; @@ -57,9 +60,10 @@ import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; 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.HostedProviders; import com.oracle.graal.pointsto.util.AnalysisError; +import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.ResolvedJavaField; @@ -97,6 +101,11 @@ public class InlineBeforeAnalysisMethodScope extends PEMethodScope { } } + private Field dmhStaticAccessorOffsetField; + private Field dmhStaticAccessorBaseField; + private AnalysisField dmhStaticAccessorOffsetAnalysisField; + private AnalysisField dmhStaticAccessorBaseAnalysisField; + protected final BigBang bb; protected final InlineBeforeAnalysisPolicy policy; @@ -172,48 +181,73 @@ protected Node doCanonicalizeFixedNode(InlineBeforeAnalysisMethodScope methodSco return node; } - /** - * Method handles do unsafe field accesses with offsets from the hosting VM which we need to - * transform to field accesses to intrinsify method handle calls in an effective way (or at all, - * even). {@link UnsafeAccessNode#canonical} would call {@link AnalysisField#getOffset}, which - * is deemed not safe in the general case and therefore not allowed. Here, however, we execute - * before analysis and can assume that any field offsets originate in the hosting VM because we - * do not assign our field offsets until after the analysis. Therefore, we can transform unsafe - * accesses by accessing the hosting VM's types and fields, using code adapted from - * {@link UnsafeAccessNode#canonical}. We cannot do the same for arrays because array offsets - * with our own object layout can be computed early on (using {@code Unsafe}, even). - */ private Node canonicalizeUnsafeAccess(UnsafeAccessNode node) { - if (!node.isCanonicalizable()) { + if (!(node.isCanonicalizable() && node.offset() instanceof LoadFieldNode offsetLoad && offsetLoad.object() != null && offsetLoad.object().isJavaConstant())) { return node; } - JavaConstant offset = node.offset().asJavaConstant(); - JavaConstant object = node.object().asJavaConstant(); - if (offset == null || object == null) { + ensureDMHStaticAccessorFieldsInitialized(); + if (!offsetLoad.field().equals(dmhStaticAccessorOffsetAnalysisField)) { return node; } - AnalysisType objectType = (AnalysisType) StampTool.typeOrNull(node.object()); - if (objectType == null || objectType.isArray()) { + JavaConstant accessorConstant = offsetLoad.object().asJavaConstant(); + Object accessor = bb.getSnippetReflectionProvider().asObject(Object.class, accessorConstant); + long offset; + Class clazz; // HotSpot-specific: field holder Class object as Unsafe.staticFieldBase() + try { + offset = dmhStaticAccessorOffsetField.getLong(accessor); + clazz = (Class) dmhStaticAccessorBaseField.get(accessor); + } catch (IllegalAccessException e) { + throw AnalysisError.shouldNotReachHere(e); + } + if (clazz == null) { return node; } - AnalysisType objectAsType = (AnalysisType) bb.getConstantReflectionProvider().asJavaType(object); - if (objectAsType != null) { - // Note: using the Class object of a type as the base object for accesses - // of that type's static fields is a HotSpot implementation detail. - ResolvedJavaType objectAsHostType = bb.getHostVM().getOriginalHostType(objectAsType); - ResolvedJavaField hostField = UnsafeAccessNode.findStaticFieldWithOffset(objectAsHostType, offset.asLong(), node.accessKind()); - return canonicalizeUnsafeAccessToField(node, hostField); + ResolvedJavaType type = GraalAccess.getOriginalProviders().getMetaAccess().lookupJavaType(clazz); + ResolvedJavaField hostField = UnsafeAccessNode.findStaticFieldWithOffset(type, offset, node.accessKind()); + if (hostField == null) { + return node; } - ResolvedJavaType objectHostType = bb.getHostVM().getOriginalHostType(objectType); - ResolvedJavaField hostField = objectHostType.findInstanceFieldWithOffset(offset.asLong(), node.accessKind()); - return canonicalizeUnsafeAccessToField(node, hostField); + AnalysisField field = bb.getUniverse().lookup(hostField); + if (field.isInternal() || field.getJavaKind() != node.accessKind()) { + return node; + } + return node.cloneAsFieldAccess(field); } - private ValueNode canonicalizeUnsafeAccessToField(UnsafeAccessNode node, ResolvedJavaField unwrappedField) { - AnalysisError.guarantee(unwrappedField != null, "Unsafe access to object header?"); - AnalysisField field = bb.getUniverse().lookup(unwrappedField); - AnalysisError.guarantee(!field.isInternal() && field.getJavaKind() == node.accessKind()); - return node.cloneAsFieldAccess(field); + @Override + protected Node handleFloatingNodeAfterAdd(MethodScope s, LoopScope loopScope, Node node) { + Node canonical = node; + if (canonical instanceof IsNullNode isNull) { + canonical = canonicalizeIsNull(isNull); + } + if (canonical != node) { + canonical.setNodeSourcePosition(node.getNodeSourcePosition()); + node.replaceAtUsagesAndDelete(canonical); + } + return super.handleFloatingNodeAfterAdd(s, loopScope, canonical); + } + + private Node canonicalizeIsNull(IsNullNode node) { + if (!(node.getValue() instanceof LoadFieldNode fieldLoad && fieldLoad.object() != null && fieldLoad.object().isJavaConstant())) { + return node; + } + ensureDMHStaticAccessorFieldsInitialized(); + if (!fieldLoad.field().equals(dmhStaticAccessorBaseAnalysisField)) { + return node; + } + // The base is always non-null, which we also assume in our field substitution. + return LogicConstantNode.contradiction(node.graph()); + } + + private void ensureDMHStaticAccessorFieldsInitialized() { + if (dmhStaticAccessorOffsetField == null) { + assert dmhStaticAccessorBaseField == null && dmhStaticAccessorOffsetAnalysisField == null && dmhStaticAccessorBaseAnalysisField == null; + Class staticAccessorClass = ReflectionUtil.lookupClass(false, "java.lang.invoke.DirectMethodHandle$StaticAccessor"); + dmhStaticAccessorOffsetField = ReflectionUtil.lookupField(staticAccessorClass, "staticOffset"); + dmhStaticAccessorBaseField = ReflectionUtil.lookupField(staticAccessorClass, "staticBase"); + dmhStaticAccessorOffsetAnalysisField = bb.getMetaAccess().lookupJavaField(dmhStaticAccessorOffsetField); + dmhStaticAccessorBaseAnalysisField = bb.getMetaAccess().lookupJavaField(dmhStaticAccessorBaseField); + } } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index dc8f1d7f3ca0..7ae5231b4005 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -139,7 +139,6 @@ import com.oracle.svm.hosted.phases.InlineBeforeAnalysisGraphDecoderImpl; import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyImpl; import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyUtils; -import com.oracle.svm.hosted.substitute.SubstitutionType; import com.oracle.svm.hosted.substitute.UnsafeAutomaticSubstitutionProcessor; import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ReflectionUtil; @@ -1017,13 +1016,4 @@ private static Class[] extractAnnotationTypes(AnalysisField field, Class[] public SimulateClassInitializerSupport createSimulateClassInitializerSupport(AnalysisMetaAccess aMetaAccess) { return new SimulateClassInitializerSupport(aMetaAccess, this); } - - @Override - public ResolvedJavaType getOriginalHostType(AnalysisType type) { - ResolvedJavaType unwrapped = super.getOriginalHostType(type); - if (unwrapped instanceof SubstitutionType substituted) { - return substituted.getOriginal(); - } - return unwrapped; - } } From f786ec71612f263465e8c3c94ee4c3ef86ba0ee3 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 20 Jul 2023 18:56:25 +0200 Subject: [PATCH 22/33] Intrinsify MemberName.getDeclaringClass rather than DirectMethodHandle.ensureInitialized to fix class initialization issues. --- .../svm/hosted/snippets/ReflectionPlugins.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java index 92a1812b9aa0..fedabc849ad1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java @@ -220,30 +220,24 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec } }); - Registration dmh = new Registration(plugins, "java.lang.invoke.DirectMethodHandle"); - dmh.register(new RequiredInvocationPlugin("ensureInitialized", Receiver.class) { + Registration dmh = new Registration(plugins, "java.lang.invoke.MemberName"); + dmh.register(new RequiredInvocationPlugin("getDeclaringClass", Receiver.class) { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver) { JavaConstant constReceiver = receiver.get().asJavaConstant(); if (constReceiver == null || constReceiver.isNull()) { return false; } - ResolvedJavaField memberField = findField(targetMethod.getDeclaringClass(), "member"); // final - JavaConstant member = b.getConstantReflection().readFieldValue(memberField, constReceiver); - if (member == null || member.isNull()) { - return false; - } /* * The clazz field of MemberName qualifies as stable except when an object is cloned * and the new object's field is nulled. We should not observe it in that state. */ - ResolvedJavaField clazzField = findField(memberField.getType().resolve(memberField.getDeclaringClass()), "clazz"); - JavaConstant clazz = b.getConstantReflection().readFieldValue(clazzField, member); - ResolvedJavaType type = b.getConstantReflection().asJavaType(clazz); - if (type == null) { + ResolvedJavaField clazzField = findField(targetMethod.getDeclaringClass(), "clazz"); + JavaConstant clazz = b.getConstantReflection().readFieldValue(clazzField, constReceiver); + if (clazz == null || clazz.isNull()) { return false; } - classInitializationPlugin.apply(b, type, () -> null, null); + b.push(JavaKind.Object, ConstantNode.forConstant(clazz, b.getMetaAccess(), b.getGraph())); return true; } }); From d68db1dccd866c7ce766a6ac24bf8a30b1bec25f Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Fri, 21 Jul 2023 13:59:00 +0200 Subject: [PATCH 23/33] Minor changes. --- .../InlineBeforeAnalysisGraphDecoder.java | 19 +++++++++++++++++++ .../phases/InlineBeforeAnalysisPolicy.java | 1 + .../core/jni/JNIGeneratedMethodSupport.java | 4 ++-- .../JNIThreadLocalPrimitiveArrayViews.java | 5 ++--- .../svm/core/jni/functions/JNIFunctions.java | 6 +++--- .../SimulateClassInitializerPolicy.java | 1 + .../oracle/svm/hosted/jni/JNIGraphKit.java | 4 ++-- .../jni/JNIPrimitiveArrayOperationMethod.java | 2 +- .../InlineBeforeAnalysisPolicyUtils.java | 11 ++++++++++- 9 files changed, 41 insertions(+), 12 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java index 312ff5fcaea0..39705a645532 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java @@ -181,6 +181,16 @@ protected Node doCanonicalizeFixedNode(InlineBeforeAnalysisMethodScope methodSco return node; } + /** + * Try to replace unsafe field accesses by offset via {@code DirectMethodHandle$StaticAccessor} + * with accesses to the actual target fields which can be constant-folded. This enables us to + * further simplify and inline through internal usages of {@code StaticAccessor} in method + * handle code itself, such as that generated by {@code InnerClassLambdaMetafactory}. A + * corresponding substitution recomputes the offsets stored in {@code StaticAccessor} objects to + * match those in the image, but it applies only much later. + * + * @see #canonicalizeIsNull + */ private Node canonicalizeUnsafeAccess(UnsafeAccessNode node) { if (!(node.isCanonicalizable() && node.offset() instanceof LoadFieldNode offsetLoad && offsetLoad.object() != null && offsetLoad.object().isJavaConstant())) { return node; @@ -227,6 +237,15 @@ protected Node handleFloatingNodeAfterAdd(MethodScope s, LoopScope loopScope, No return super.handleFloatingNodeAfterAdd(s, loopScope, canonical); } + /** + * Constant-fold null checks of {@code DirectMethodHandle$StaticAccessor.staticBase}. This + * enables us to further simplify and inline through internal usages of {@code StaticAccessor} + * in method handle code itself, such as that generated by {@code InnerClassLambdaMetafactory}. + * A corresponding substitution computes the final value of {@code staticBase} in the image, but + * it applies only much later, and we know that it will never be {@code null}. + * + * @see #canonicalizeUnsafeAccess + */ private Node canonicalizeIsNull(IsNullNode node) { if (!(node.getValue() instanceof LoadFieldNode fieldLoad && fieldLoad.object() != null && fieldLoad.object().isJavaConstant())) { return node; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java index 8469a4b81ff1..466f14253a51 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java @@ -109,6 +109,7 @@ protected InlineBeforeAnalysisPolicy(NodePlugin[] nodePlugins) { protected abstract AbstractPolicyScope openCalleeScope(AbstractPolicyScope outer, AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, boolean[] constArgsWithReceiver, boolean intrinsifiedMethodHandle); + /** @see InlineBeforeAnalysisGraphDecoder#shouldOmitIntermediateMethodInStates */ protected boolean shouldOmitIntermediateMethodInState(ResolvedJavaMethod method) { return false; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIGeneratedMethodSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIGeneratedMethodSupport.java index 56e6fd4c7d35..d1551e7ef54a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIGeneratedMethodSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIGeneratedMethodSupport.java @@ -119,8 +119,8 @@ static PointerBase createArrayViewAndGetAddress(Object array, CCharPointer isCop return WordFactory.nullPointer(); } - static boolean destroyArrayViewByAddress(PointerBase address, int mode) throws Throwable { - return JNIThreadLocalPrimitiveArrayViews.destroyArrayViewByAddress(address, mode); + static void destroyNewestArrayViewByAddress(PointerBase address, int mode) throws Throwable { + JNIThreadLocalPrimitiveArrayViews.destroyNewestArrayViewByAddress(address, mode); } static void getPrimitiveArrayRegion(JavaKind elementKind, Object array, int start, int count, PointerBase buffer) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIThreadLocalPrimitiveArrayViews.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIThreadLocalPrimitiveArrayViews.java index a27456937f20..890e7d0737e2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIThreadLocalPrimitiveArrayViews.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIThreadLocalPrimitiveArrayViews.java @@ -58,7 +58,7 @@ public static T createArrayViewAndGetAddress(Object arra return createArrayView(array).addressOfArrayElement(0); } - public static boolean destroyArrayViewByAddress(PointerBase address, int mode) { + public static void destroyNewestArrayViewByAddress(PointerBase address, int mode) { ReferencedObjectListNode previous = null; ReferencedObjectListNode current = referencedObjectsListHead.get(); while (current != null) { @@ -77,12 +77,11 @@ public static boolean destroyArrayViewByAddress(PointerBase address, int mode) { } else { current.object.untrack(); } - return true; + return; } previous = current; current = current.next; } - return false; } static int getCount() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java index 305223183e3c..1a9d15eec025 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java @@ -554,7 +554,7 @@ static CCharPointer GetStringUTFChars(JNIEnvironment env, JNIObjectHandle hstr, @CEntryPoint(exceptionHandler = JNIExceptionHandlerVoid.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = Publish.NotPublished) @CEntryPointOptions(prologue = JNIEnvEnterFatalOnFailurePrologue.class) static void ReleaseStringUTFChars(JNIEnvironment env, JNIObjectHandle hstr, CCharPointer chars) { - JNIThreadLocalPrimitiveArrayViews.destroyArrayViewByAddress(chars, JNIMode.JNI_ABORT()); + JNIThreadLocalPrimitiveArrayViews.destroyNewestArrayViewByAddress(chars, JNIMode.JNI_ABORT()); } /* @@ -801,7 +801,7 @@ static WordPointer GetPrimitiveArrayCritical(JNIEnvironment env, JNIObjectHandle @CEntryPoint(exceptionHandler = JNIExceptionHandlerVoid.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = Publish.NotPublished) @CEntryPointOptions(prologue = JNIEnvEnterFatalOnFailurePrologue.class) static void ReleasePrimitiveArrayCritical(JNIEnvironment env, JNIObjectHandle harray, WordPointer carray, int mode) { - JNIThreadLocalPrimitiveArrayViews.destroyArrayViewByAddress(carray, mode); + JNIThreadLocalPrimitiveArrayViews.destroyNewestArrayViewByAddress(carray, mode); } /* @@ -1329,7 +1329,7 @@ static CShortPointer getNulTerminatedStringCharsAndPin(JNIObjectHandle hstr, CCh } static void releaseString(CShortPointer cstr) { - JNIThreadLocalPrimitiveArrayViews.destroyArrayViewByAddress(cstr, JNIMode.JNI_ABORT()); + JNIThreadLocalPrimitiveArrayViews.destroyNewestArrayViewByAddress(cstr, JNIMode.JNI_ABORT()); } @Uninterruptible(reason = "exception handler") diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java index a43c9f28d0d4..61f2bb4c61d9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java @@ -93,6 +93,7 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met @Override protected boolean shouldInterpretMethodHandleInvoke(ResolvedJavaMethod method, MethodHandleWithExceptionNode node) { + /* Same as elsewhere, we inline greedily. */ return true; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIGraphKit.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIGraphKit.java index 077cc2d166f9..dc5c0bd0772a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIGraphKit.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIGraphKit.java @@ -214,8 +214,8 @@ public InvokeWithExceptionNode createArrayViewAndGetAddress(ValueNode array, Val return createStaticInvoke("createArrayViewAndGetAddress", array, isCopy); } - public InvokeWithExceptionNode destroyArrayViewByAddress(ValueNode address, ValueNode mode) { - return createStaticInvoke("destroyArrayViewByAddress", address, mode); + public InvokeWithExceptionNode destroyNewestArrayViewByAddress(ValueNode address, ValueNode mode) { + return createStaticInvoke("destroyNewestArrayViewByAddress", address, mode); } public FixedWithNextNode getPrimitiveArrayRegionRetainException(JavaKind elementKind, ValueNode array, ValueNode start, ValueNode count, ValueNode buffer) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIPrimitiveArrayOperationMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIPrimitiveArrayOperationMethod.java index 5b46cee76620..ec43c16dadcc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIPrimitiveArrayOperationMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIPrimitiveArrayOperationMethod.java @@ -197,7 +197,7 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, case RELEASE_ELEMENTS: { ValueNode address = arguments.get(2); ValueNode mode = arguments.get(3); - kit.destroyArrayViewByAddress(address, mode); + kit.destroyNewestArrayViewByAddress(address, mode); break; } case GET_REGION: diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java index 92885c56cada..0b669ca7b048 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java @@ -85,6 +85,12 @@ public static class Options { @Option(help = "Maximum call depth for method inlined before static analysis")// public static final HostedOptionKey InlineBeforeAnalysisAllowedDepth = new HostedOptionKey<>(20); + + @Option(help = "Maximum number of computation nodes for method handle internals inlined before static analysis")// + public static final HostedOptionKey InlineBeforeAnalysisMethodHandleAllowedNodes = new HostedOptionKey<>(100); + + @Option(help = "Maximum number of invokes for method handle internals inlined before static analysis")// + public static final HostedOptionKey InlineBeforeAnalysisMethodHandleAllowedInvokes = new HostedOptionKey<>(20); } @SuppressWarnings("unchecked") // @@ -193,7 +199,10 @@ static AccumulativeCounters create(AccumulativeCounters outer) { } static AccumulativeCounters createForMethodHandleIntrinsification(AccumulativeCounters outer) { - return new AccumulativeCounters(100, 20, outer.maxInliningDepth, true); + return new AccumulativeCounters(Options.InlineBeforeAnalysisMethodHandleAllowedNodes.getValue(), + Options.InlineBeforeAnalysisMethodHandleAllowedInvokes.getValue(), + outer.maxInliningDepth, + true); } int maxNodes; From a790f1ea38fda9c660590aaa4a28d0a72a5c1dce Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Fri, 21 Jul 2023 14:11:55 +0200 Subject: [PATCH 24/33] Replace more lambdas which use word types. --- .../IsolatedCompilableTruffleAST.java | 9 ++++++--- .../IsolatedTruffleRuntimeSupport.java | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedCompilableTruffleAST.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedCompilableTruffleAST.java index 91bece15a676..c11c27d5feaa 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedCompilableTruffleAST.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedCompilableTruffleAST.java @@ -170,9 +170,12 @@ private static ClientHandle getCompilationSpeculationLog0(@Suppr private static void onCompilationFailed0(@SuppressWarnings("unused") ClientIsolateThread client, ClientHandle compilableHandle, CompilerHandle> serializedExceptionHandle, boolean silent, boolean bailout, boolean permanentBailout, boolean graphTooBig) { - Supplier serializedException = () -> { - ClientHandle resultHandle = getReasonAndStackTrace0(IsolatedCompileClient.get().getCompiler(), serializedExceptionHandle); - return IsolatedCompileClient.get().unhand(resultHandle); + Supplier serializedException = new Supplier<>() { + @Override + public String get() { + ClientHandle resultHandle = getReasonAndStackTrace0(IsolatedCompileClient.get().getCompiler(), serializedExceptionHandle); + return IsolatedCompileClient.get().unhand(resultHandle); + } }; IsolatedCompileClient.get().unhand(compilableHandle).onCompilationFailed(serializedException, silent, bailout, permanentBailout, graphTooBig); } diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedTruffleRuntimeSupport.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedTruffleRuntimeSupport.java index a41b2cbc7af4..322cca7b8371 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedTruffleRuntimeSupport.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/isolated/IsolatedTruffleRuntimeSupport.java @@ -74,16 +74,19 @@ public static Consumer registerOptimizedAssumptio if (consumerHandle.equal(IsolatedHandles.nullHandle())) { return null; } - return codeInstallBridge -> { - ClientHandle installedCodeHandle = IsolatedHandles.nullHandle(); - if (codeInstallBridge != null) { - installedCodeHandle = ((IsolatedCodeInstallBridge) codeInstallBridge).getSubstrateInstalledCodeHandle(); - } + return new Consumer<>() { + @Override + public void accept(OptimizedAssumptionDependency codeInstallBridge) { + ClientHandle installedCodeHandle = IsolatedHandles.nullHandle(); + if (codeInstallBridge != null) { + installedCodeHandle = ((IsolatedCodeInstallBridge) codeInstallBridge).getSubstrateInstalledCodeHandle(); + } - @SuppressWarnings("unchecked") - ClientHandle dependencyAccessHandle = (ClientHandle) installedCodeHandle; + @SuppressWarnings("unchecked") + ClientHandle dependencyAccessHandle = (ClientHandle) installedCodeHandle; - notifyAssumption0(IsolatedCompileContext.get().getClient(), consumerHandle, dependencyAccessHandle); + notifyAssumption0(IsolatedCompileContext.get().getClient(), consumerHandle, dependencyAccessHandle); + } }; } From 81b84b7db92d91d8ffd24c22085da5ce30c69f90 Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Fri, 21 Jul 2023 16:22:51 +0200 Subject: [PATCH 25/33] Don't use new MethodHandle support with ParseOnceJIT. --- .../src/com/oracle/svm/hosted/NativeImageGenerator.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index ad48a262744d..86397ba1b58a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -193,6 +193,7 @@ import com.oracle.svm.core.c.struct.OffsetOf; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.cpufeature.RuntimeCPUFeatureCheck; +import com.oracle.svm.core.deopt.DeoptimizationSupport; import com.oracle.svm.core.graal.EconomyGraalConfiguration; import com.oracle.svm.core.graal.GraalConfiguration; import com.oracle.svm.core.graal.code.SubstrateBackend; @@ -1310,7 +1311,10 @@ public static void registerGraphBuilderPlugins(FeatureHandler featureHandler, Ru SubstrateReplacements replacements = (SubstrateReplacements) providers.getReplacements(); plugins.appendInlineInvokePlugin(replacements); - if (SubstrateOptions.parseOnce() && InlineBeforeAnalysis.Options.InlineBeforeAnalysis.getValue(aUniverse.getBigbang().getOptions())) { + boolean useInlineBeforeAnalysisMethodHandleSupport = SubstrateOptions.parseOnce() && + InlineBeforeAnalysis.Options.InlineBeforeAnalysis.getValue(aUniverse.getBigbang().getOptions()) && + !DeoptimizationSupport.enabled(); + if (useInlineBeforeAnalysisMethodHandleSupport) { if (reason.duringAnalysis()) { plugins.appendNodePlugin(new MethodHandleWithExceptionPlugin(providers.getConstantReflection().getMethodHandleAccess(), false)); } From 632d9adeaba538eb700847a23a0d8ecdecac2c56 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Fri, 21 Jul 2023 23:42:32 +0200 Subject: [PATCH 26/33] Always interpret an unintrinsified method handle invocation. --- .../phases/InlineBeforeAnalysisGraphDecoder.java | 7 ------- .../pointsto/phases/InlineBeforeAnalysisPolicy.java | 9 --------- .../SimulateClassInitializerPolicy.java | 7 ------- .../phases/InlineBeforeAnalysisPolicyUtils.java | 11 ----------- 4 files changed, 34 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java index 39705a645532..c019774136c7 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisGraphDecoder.java @@ -317,14 +317,7 @@ protected LoopScope handleMethodHandle(MethodScope s, LoopScope loopScope, Invok MethodHandleWithExceptionNode node = invokableData.invoke; Node replacement = node.trySimplify(providers.getConstantReflection().getMethodHandleAccess()); boolean intrinsifiedMethodHandle = (replacement != node); - InlineBeforeAnalysisMethodScope methodScope = cast(s); if (!intrinsifiedMethodHandle) { - if (!methodScope.inliningAborted && methodScope.isInlinedMethod()) { - if (!methodScope.policyScope.shouldInterpretMethodHandleInvoke(methodScope.method, node)) { - abortInlining(methodScope); - return loopScope; - } - } replacement = node.replaceWithInvoke().asNode(); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java index 466f14253a51..34ba7a30e96b 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/phases/InlineBeforeAnalysisPolicy.java @@ -30,7 +30,6 @@ import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext; import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin.InlineInfo; import org.graalvm.compiler.nodes.graphbuilderconf.NodePlugin; -import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.util.AnalysisError; @@ -78,14 +77,6 @@ protected AbstractPolicyScope(int inliningDepth) { * decision on the current list of usages. The list of usages is often but not always empty. */ public abstract boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod method, Node node); - - /** - * The given {@link MethodHandleWithExceptionNode} could not be intrinsified. If this method - * returns {@code true}, the node is instead replaced with a call into the method handle - * interpreter and inlining continues. If this method returns {@code false}, inlining is - * aborted. - */ - protected abstract boolean shouldInterpretMethodHandleInvoke(ResolvedJavaMethod method, MethodHandleWithExceptionNode node); } protected final NodePlugin[] nodePlugins; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java index 61f2bb4c61d9..9b02563fdcd9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerPolicy.java @@ -30,7 +30,6 @@ import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext; import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin; import org.graalvm.compiler.nodes.graphbuilderconf.NodePlugin; -import org.graalvm.compiler.replacements.nodes.MethodHandleWithExceptionNode; import org.graalvm.nativeimage.AnnotationAccess; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; @@ -91,12 +90,6 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met return true; } - @Override - protected boolean shouldInterpretMethodHandleInvoke(ResolvedJavaMethod method, MethodHandleWithExceptionNode node) { - /* Same as elsewhere, we inline greedily. */ - return true; - } - @Override public String toString() { return "allocatedBytes: " + allocatedBytes + " (" + accumulativeCounters.allocatedBytes + ")"; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java index 0b669ca7b048..332718ea10c9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java @@ -177,12 +177,6 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met return true; } - @Override - protected boolean shouldInterpretMethodHandleInvoke(ResolvedJavaMethod method, MethodHandleWithExceptionNode node) { - // always inlining - return true; - } - @Override public String toString() { return "AlwaysInlineScope"; @@ -470,11 +464,6 @@ public boolean processNode(AnalysisMetaAccess metaAccess, ResolvedJavaMethod met return allow || accumulativeCounters.inMethodHandleIntrinsification; } - @Override - protected boolean shouldInterpretMethodHandleInvoke(ResolvedJavaMethod method, MethodHandleWithExceptionNode node) { - return false; - } - @Override public String toString() { return "AccumulativeInlineScope: " + numNodes + "/" + numInvokes + " (" + accumulativeCounters.numNodes + "/" + accumulativeCounters.numInvokes + ")"; From 4a7617074f48d0c50e13b675aed4c21cbdd957ba Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 25 Jul 2023 18:49:36 +0200 Subject: [PATCH 27/33] Register MemberName objects left behind by MethodHandle objects eliminated by optimizations. --- .../methodhandles/MethodHandleFeature.java | 119 ++++-------------- 1 file changed, 25 insertions(+), 94 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java index c3dc04690f1c..f72b636399fc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java @@ -33,7 +33,6 @@ import java.lang.reflect.Method; import java.util.Iterator; import java.util.Optional; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -82,32 +81,18 @@ @SuppressWarnings("unused") public class MethodHandleFeature implements InternalFeature { - private Set seenMethodHandles; - private Class directMethodHandleClass; - private Class boundMethodHandleClass; - private Class delegatingMethodHandleClass; - private Method getDelegatingMethodHandleTarget; - private Method methodHandleInternalMemberName; + private Class memberNameClass; private Method memberNameGetDeclaringClass; private Method memberNameGetName; private Method memberNameIsMethod; private Method memberNameIsConstructor; private Method memberNameIsField; private Method memberNameGetMethodType; - private Field methodHandleInternalForm; - private Field lambdaFormNames; - private Field lambdaFormArity; - private Field nameFunction; - private Field namedFunctionMemberName; private Field lambdaFormLFIdentity; private Field lambdaFormLFZero; private Field lambdaFormNFIdentity; private Field lambdaFormNFZero; private Field typedAccessors; - private Field classSpecializerCache; - private Class lambdaFormClass; - private Class classSpecializerClass; - private Class arrayAccessorClass; /** * A new {@link MethodType} interning table which contains only objects that are already part of @@ -121,15 +106,7 @@ public class MethodHandleFeature implements InternalFeature { @Override public void duringSetup(DuringSetupAccess access) { - seenMethodHandles = ConcurrentHashMap.newKeySet(); - directMethodHandleClass = access.findClassByName("java.lang.invoke.DirectMethodHandle"); - boundMethodHandleClass = access.findClassByName("java.lang.invoke.BoundMethodHandle"); - delegatingMethodHandleClass = access.findClassByName("java.lang.invoke.DelegatingMethodHandle"); - getDelegatingMethodHandleTarget = ReflectionUtil.lookupMethod(delegatingMethodHandleClass, "getTarget"); - methodHandleInternalMemberName = ReflectionUtil.lookupMethod(MethodHandle.class, "internalMemberName"); - methodHandleInternalForm = ReflectionUtil.lookupField(MethodHandle.class, "form"); - - Class memberNameClass = access.findClassByName("java.lang.invoke.MemberName"); + memberNameClass = access.findClassByName("java.lang.invoke.MemberName"); memberNameGetDeclaringClass = ReflectionUtil.lookupMethod(memberNameClass, "getDeclaringClass"); memberNameGetName = ReflectionUtil.lookupMethod(memberNameClass, "getName"); memberNameIsMethod = ReflectionUtil.lookupMethod(memberNameClass, "isMethod"); @@ -137,23 +114,14 @@ public void duringSetup(DuringSetupAccess access) { memberNameIsField = ReflectionUtil.lookupMethod(memberNameClass, "isField"); memberNameGetMethodType = ReflectionUtil.lookupMethod(memberNameClass, "getMethodType"); - lambdaFormClass = access.findClassByName("java.lang.invoke.LambdaForm"); - lambdaFormNames = ReflectionUtil.lookupField(lambdaFormClass, "names"); - lambdaFormArity = ReflectionUtil.lookupField(lambdaFormClass, "arity"); - Class nameClass = access.findClassByName("java.lang.invoke.LambdaForm$Name"); - nameFunction = ReflectionUtil.lookupField(nameClass, "function"); - Class namedFunctionClass = access.findClassByName("java.lang.invoke.LambdaForm$NamedFunction"); - namedFunctionMemberName = ReflectionUtil.lookupField(namedFunctionClass, "member"); - + Class lambdaFormClass = access.findClassByName("java.lang.invoke.LambdaForm"); lambdaFormLFIdentity = ReflectionUtil.lookupField(lambdaFormClass, "LF_identity"); lambdaFormLFZero = ReflectionUtil.lookupField(lambdaFormClass, "LF_zero"); lambdaFormNFIdentity = ReflectionUtil.lookupField(lambdaFormClass, "NF_identity"); lambdaFormNFZero = ReflectionUtil.lookupField(lambdaFormClass, "NF_zero"); - classSpecializerClass = access.findClassByName("java.lang.invoke.ClassSpecializer"); - arrayAccessorClass = access.findClassByName("java.lang.invoke.MethodHandleImpl$ArrayAccessor"); + Class arrayAccessorClass = access.findClassByName("java.lang.invoke.MethodHandleImpl$ArrayAccessor"); typedAccessors = ReflectionUtil.lookupField(arrayAccessorClass, "TYPED_ACCESSORS"); - classSpecializerCache = ReflectionUtil.lookupField(classSpecializerClass, "cache"); Class concurrentWeakInternSetClass = access.findClassByName("java.lang.invoke.MethodType$ConcurrentWeakInternSet"); runtimeMethodTypeInternTable = ReflectionUtil.newInstance(concurrentWeakInternSetClass); @@ -203,7 +171,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { access.registerSubtypeReachabilityHandler(MethodHandleFeature::registerVarHandleMethodsForReflection, access.findClassByName("java.lang.invoke.VarHandle")); - access.registerSubtypeReachabilityHandler(MethodHandleFeature::scanBoundMethodHandle, boundMethodHandleClass); + access.registerSubtypeReachabilityHandler(MethodHandleFeature::scanBoundMethodHandle, + access.findClassByName("java.lang.invoke.BoundMethodHandle")); AnalysisMetaAccess metaAccess = ((FeatureImpl.BeforeAnalysisAccessImpl) access).getMetaAccess(); access.registerFieldValueTransformer( @@ -348,8 +317,15 @@ private Object registerSeenObject(Object obj) { if (!BuildPhaseProvider.isAnalysisFinished()) { if (obj instanceof MethodType mt) { registerMethodType(mt); - } else { - registerMethodHandleRecurse(obj); + } else if (memberNameClass.isInstance(obj)) { + /* + * We used to register only MemberName instances which are reachable from a + * MethodHandle, but optimizations can eliminate a MethodHandle object in code which + * we might never see otherwise and leave a MemberName object behind which is still + * used for a call. Therefore, we register all MemberName instances in the image + * heap, which should only be reachable via MethodHandle objects, in any case. + */ + registerMemberName(obj); } } return obj; @@ -363,61 +339,6 @@ private void registerMethodType(MethodType methodType) { } } - private void registerMethodHandleRecurse(Object obj) { - if (!(obj instanceof MethodHandle) || seenMethodHandles.contains(obj)) { - return; - } - MethodHandle handle = (MethodHandle) obj; - seenMethodHandles.add(handle); - - if (directMethodHandleClass.isAssignableFrom(handle.getClass())) { - try { - registerMemberName(methodHandleInternalMemberName.invoke(handle)); - } catch (IllegalAccessException | InvocationTargetException e) { - throw VMError.shouldNotReachHere(e); - } - } else if (boundMethodHandleClass.isAssignableFrom(handle.getClass())) { - /* Allow access to species class args at runtime */ - for (Field field : handle.getClass().getDeclaredFields()) { - if (field.getName().startsWith("arg")) { - RuntimeReflection.register(field); - if (!field.getType().isPrimitive()) { - try { - field.setAccessible(true); - registerMethodHandleRecurse(field.get(handle)); - } catch (IllegalAccessException e) { - throw VMError.shouldNotReachHere(e); - } - } - } - } - /* Recursively register all methods called by the handle */ - try { - Object form = methodHandleInternalForm.get(handle); - Object[] names = (Object[]) lambdaFormNames.get(form); - int arity = (int) lambdaFormArity.get(form); - for (int i = arity; i < names.length; ++i) { - Object function = nameFunction.get(names[i]); - if (function != null) { - Object memberName = namedFunctionMemberName.get(function); - if (memberName != null) { - registerMemberName(memberName); - } - } - } - } catch (IllegalAccessException e) { - VMError.shouldNotReachHere(e); - } - } else if (delegatingMethodHandleClass.isAssignableFrom(handle.getClass())) { - try { - MethodHandle wrappedHandle = (MethodHandle) getDelegatingMethodHandleTarget.invoke(handle); - registerMethodHandleRecurse(wrappedHandle); - } catch (IllegalAccessException | InvocationTargetException e) { - throw VMError.shouldNotReachHere(e); - } - } - } - private void registerMemberName(Object memberName) { try { Class declaringClass = (Class) memberNameGetDeclaringClass.invoke(memberName); @@ -456,6 +377,16 @@ public void duringAnalysis(DuringAnalysisAccess a) { } private static void scanBoundMethodHandle(DuringAnalysisAccess a, Class bmhSubtype) { + /* Allow access to species class args at runtime */ + for (Field field : bmhSubtype.getDeclaredFields()) { + if (field.getName().startsWith("arg")) { + RuntimeReflection.register(field); + if (!field.getType().isPrimitive()) { + field.setAccessible(true); + } + } + } + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; access.getBigBang().postTask(unused -> { Field bmhSpeciesField = ReflectionUtil.lookupField(true, bmhSubtype, "BMH_SPECIES"); From 4e4a34fe12e86731a05efe4eecf51e16423ffec9 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 25 Jul 2023 19:52:05 +0200 Subject: [PATCH 28/33] Add option for old method handle intrinsics. --- .../src/com/oracle/svm/core/SubstrateOptions.java | 3 +++ .../src/com/oracle/svm/hosted/NativeImageGenerator.java | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index f0f71326aff1..4dac68120b6c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -967,4 +967,7 @@ public enum ReportingMode { @Option(help = "Enable and disable normal processing of flags relating to experimental options.", type = OptionType.Expert, stability = OptionStability.EXPERIMENTAL) // public static final HostedOptionKey UnlockExperimentalVMOptions = new HostedOptionKey<>(false); + + @Option(help = "Force using legacy method handle intrinsics.", type = Expert) // + public static final HostedOptionKey UseOldMethodHandleIntrinsics = new HostedOptionKey<>(false); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 7a7a0342f9aa..d43594d24133 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -1313,7 +1313,8 @@ public static void registerGraphBuilderPlugins(FeatureHandler featureHandler, Ru SubstrateReplacements replacements = (SubstrateReplacements) providers.getReplacements(); plugins.appendInlineInvokePlugin(replacements); - boolean useInlineBeforeAnalysisMethodHandleSupport = SubstrateOptions.parseOnce() && + boolean useInlineBeforeAnalysisMethodHandleSupport = !SubstrateOptions.UseOldMethodHandleIntrinsics.getValue() && + SubstrateOptions.parseOnce() && InlineBeforeAnalysis.Options.InlineBeforeAnalysis.getValue(aUniverse.getBigbang().getOptions()) && !DeoptimizationSupport.enabled(); if (useInlineBeforeAnalysisMethodHandleSupport) { From c959b19ef7c00d38e5d38f7d3cee66279cc64adf Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 3 Aug 2023 20:26:11 +0200 Subject: [PATCH 29/33] Use image heap scanner to find method handle objects that are actually reachable. --- .../oracle/svm/core/jdk/VarHandleFeature.java | 27 +++++++----- .../svm/hosted/heap/SVMImageHeapScanner.java | 22 ++++++++++ .../methodhandles/MethodHandleFeature.java | 44 ++++++------------- 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java index c6c22af9797e..ce0d102c1ded 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java @@ -25,6 +25,7 @@ package com.oracle.svm.core.jdk; import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.HashMap; @@ -76,11 +77,10 @@ * unsafe accessed so that our static analysis is correct, and 2) recompute the field offsets from * the hosted offsets to the runtime offsets. Luckily, we have all information to reconstruct the * original {@link Field} (see {@link #findVarHandleField}). The registration for unsafe access - * happens in an object replacer: the method {@link #processVarHandle} is called for every object - * (and therefore every VarHandle) that is reachable in the image heap. The field offset - * recomputations are registered for all classes manually (a bit of code duplication on our side), - * but all recomputations use the same custom field value recomputation handler: - * {@link VarHandleFieldOffsetComputer}. + * happens in {@link #processReachableHandle} which is called for every relevant object once it + * becomes reachable and so part of the image heap. The field offset recomputations are registered + * for all classes manually (a bit of code duplication on our side), but all recomputations use the + * same custom field value recomputation handler: {@link VarHandleFieldOffsetComputer}. * * For static fields, also the base of the Unsafe access needs to be changed to the static field * holder arrays defined in {@link StaticFieldsSupport}. We cannot do a recomputation to the actual @@ -187,11 +187,6 @@ Field findVarHandleField(Object varHandle) { throw VMError.shouldNotReachHere("Could not find field referenced in VarHandle: " + type + ", offset = " + originalFieldOffset + ", isStatic = " + info.isStatic); } - @Override - public void duringSetup(DuringSetupAccess access) { - access.registerObjectReplacer(this::processVarHandle); - } - @Override public void beforeAnalysis(BeforeAnalysisAccess access) { markAsUnsafeAccessed = access::registerAsUnsafeAccessed; @@ -202,13 +197,21 @@ public void afterAnalysis(AfterAnalysisAccess access) { markAsUnsafeAccessed = null; } + public void registerHeapVarHandle(VarHandle varHandle) { + processReachableHandle(varHandle); + } + + public void registerHeapMethodHandle(MethodHandle directMethodHandle) { + processReachableHandle(directMethodHandle); + } + /** * Register all fields accessed by a VarHandle for an instance field or a static field as unsafe * accessed, which is necessary for correctness of the static analysis. We want to process every - * VarHandle only once, therefore we mark all VarHandle that were already processed in in + * VarHandle only once, therefore we mark all VarHandle that were already processed in * {@link #processedVarHandles}. */ - private Object processVarHandle(Object obj) { + private Object processReachableHandle(Object obj) { VarHandleInfo info = infos.get(obj.getClass()); if (info != null && processedVarHandles.putIfAbsent(obj, true) == null) { VMError.guarantee(markAsUnsafeAccessed != null, "New VarHandle found after static analysis"); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java index ee7961fd0229..40280dd3d0bd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageHeapScanner.java @@ -24,8 +24,12 @@ */ package com.oracle.svm.hosted.heap; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; import java.lang.reflect.Executable; import java.lang.reflect.Field; +import java.lang.reflect.Member; import java.util.function.Consumer; import org.graalvm.collections.EconomicMap; @@ -44,11 +48,13 @@ import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.jdk.VarHandleFeature; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; import com.oracle.svm.hosted.ameta.ReadableJavaField; import com.oracle.svm.hosted.meta.HostedMetaAccess; +import com.oracle.svm.hosted.methodhandles.MethodHandleFeature; import com.oracle.svm.hosted.reflect.ReflectionHostedSupport; import com.oracle.svm.util.ReflectionUtil; @@ -63,6 +69,10 @@ public class SVMImageHeapScanner extends ImageHeapScanner { private final Field economicMapImplEntriesField; private final Field economicMapImplHashArrayField; private final ReflectionHostedSupport reflectionSupport; + private final Class memberNameClass; + private final MethodHandleFeature methodHandleSupport; + private final Class directMethodHandleClass; + private final VarHandleFeature varHandleSupport; @SuppressWarnings("this-escape") public SVMImageHeapScanner(BigBang bb, ImageHeap imageHeap, ImageClassLoader loader, AnalysisMetaAccess metaAccess, @@ -74,6 +84,10 @@ public SVMImageHeapScanner(BigBang bb, ImageHeap imageHeap, ImageClassLoader loa economicMapImplHashArrayField = ReflectionUtil.lookupField(economicMapImpl, "hashArray"); ImageSingletons.add(ImageHeapScanner.class, this); reflectionSupport = ImageSingletons.lookup(ReflectionHostedSupport.class); + memberNameClass = getClass("java.lang.invoke.MemberName"); + methodHandleSupport = ImageSingletons.lookup(MethodHandleFeature.class); + directMethodHandleClass = getClass("java.lang.invoke.DirectMethodHandle"); + varHandleSupport = ImageSingletons.lookup(VarHandleFeature.class); } public static ImageHeapScanner instance() { @@ -149,6 +163,14 @@ protected void onObjectReachable(ImageHeapConstant imageHeapConstant, ScanReason reflectionSupport.registerHeapReflectionExecutable(executable, reason); } else if (object instanceof DynamicHub hub) { reflectionSupport.registerHeapDynamicHub(hub, reason); + } else if (object instanceof VarHandle varHandle) { + varHandleSupport.registerHeapVarHandle(varHandle); + } else if (directMethodHandleClass.isInstance(object)) { + varHandleSupport.registerHeapMethodHandle((MethodHandle) object); + } else if (object instanceof MethodType methodType) { + methodHandleSupport.registerHeapMethodType(methodType); + } else if (memberNameClass.isInstance(object)) { + methodHandleSupport.registerHeapMemberName((Member) object); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java index f72b636399fc..6434dea55452 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java @@ -30,6 +30,7 @@ import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.Iterator; import java.util.Optional; @@ -39,7 +40,6 @@ import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; @@ -81,9 +81,6 @@ @SuppressWarnings("unused") public class MethodHandleFeature implements InternalFeature { - private Class memberNameClass; - private Method memberNameGetDeclaringClass; - private Method memberNameGetName; private Method memberNameIsMethod; private Method memberNameIsConstructor; private Method memberNameIsField; @@ -106,9 +103,7 @@ public class MethodHandleFeature implements InternalFeature { @Override public void duringSetup(DuringSetupAccess access) { - memberNameClass = access.findClassByName("java.lang.invoke.MemberName"); - memberNameGetDeclaringClass = ReflectionUtil.lookupMethod(memberNameClass, "getDeclaringClass"); - memberNameGetName = ReflectionUtil.lookupMethod(memberNameClass, "getName"); + Class memberNameClass = access.findClassByName("java.lang.invoke.MemberName"); memberNameIsMethod = ReflectionUtil.lookupMethod(memberNameClass, "isMethod"); memberNameIsConstructor = ReflectionUtil.lookupMethod(memberNameClass, "isConstructor"); memberNameIsField = ReflectionUtil.lookupMethod(memberNameClass, "isField"); @@ -126,8 +121,6 @@ public void duringSetup(DuringSetupAccess access) { Class concurrentWeakInternSetClass = access.findClassByName("java.lang.invoke.MethodType$ConcurrentWeakInternSet"); runtimeMethodTypeInternTable = ReflectionUtil.newInstance(concurrentWeakInternSetClass); concurrentWeakInternSetAdd = ReflectionUtil.lookupMethod(concurrentWeakInternSetClass, "add", Object.class); - - access.registerObjectReplacer(this::registerSeenObject); } @Override @@ -313,25 +306,7 @@ private static void registerVarHandleMethodsForReflection(FeatureAccess access, } } - private Object registerSeenObject(Object obj) { - if (!BuildPhaseProvider.isAnalysisFinished()) { - if (obj instanceof MethodType mt) { - registerMethodType(mt); - } else if (memberNameClass.isInstance(obj)) { - /* - * We used to register only MemberName instances which are reachable from a - * MethodHandle, but optimizations can eliminate a MethodHandle object in code which - * we might never see otherwise and leave a MemberName object behind which is still - * used for a call. Therefore, we register all MemberName instances in the image - * heap, which should only be reachable via MethodHandle objects, in any case. - */ - registerMemberName(obj); - } - } - return obj; - } - - private void registerMethodType(MethodType methodType) { + public void registerHeapMethodType(MethodType methodType) { try { concurrentWeakInternSetAdd.invoke(runtimeMethodTypeInternTable, methodType); } catch (ReflectiveOperationException e) { @@ -339,13 +314,20 @@ private void registerMethodType(MethodType methodType) { } } - private void registerMemberName(Object memberName) { + public void registerHeapMemberName(Member memberName) { + /* + * We used to register only MemberName instances which are reachable from MethodHandle + * objects, but code optimizations can eliminate a MethodHandle object which might never + * become reachable otherwise and leave a MemberName object behind which is still used for a + * call or field access. Therefore, we register all MemberName instances in the image heap, + * which should only come into existence via MethodHandle objects, in any case. + */ try { - Class declaringClass = (Class) memberNameGetDeclaringClass.invoke(memberName); + Class declaringClass = memberName.getDeclaringClass(); boolean isMethod = (boolean) memberNameIsMethod.invoke(memberName); boolean isConstructor = (boolean) memberNameIsConstructor.invoke(memberName); boolean isField = (boolean) memberNameIsField.invoke(memberName); - String name = (isMethod || isField) ? (String) memberNameGetName.invoke(memberName) : null; + String name = (isMethod || isField) ? memberName.getName() : null; Class[] paramTypes = null; if (isMethod || isConstructor) { MethodType methodType = (MethodType) memberNameGetMethodType.invoke(memberName); From d091e6461027354fffc78bab173ecc1b318e641c Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Wed, 2 Aug 2023 17:38:30 +0200 Subject: [PATCH 30/33] More eagerly clear exception object frame states. --- .../graalvm/compiler/java/BytecodeParser.java | 25 +++++++++++++++---- .../compiler/java/FrameStateBuilder.java | 3 +++ .../phases/AnalysisGraphBuilderPhase.java | 22 ++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/java/BytecodeParser.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/java/BytecodeParser.java index 18799a5debfc..9db05726e7db 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/java/BytecodeParser.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/java/BytecodeParser.java @@ -1338,6 +1338,16 @@ protected void handleUnresolvedInvoke(JavaMethod javaMethod, InvokeKind invokeKi deopt.updateNodeSourcePosition(() -> createBytecodePosition()); } + protected FrameStateBuilder createFrameStateForExceptionHandling(@SuppressWarnings("unused") int bci) { + FrameStateBuilder dispatchState = frameState.copy(); + dispatchState.clearStack(); + return dispatchState; + } + + protected void clearNonLiveLocals(FrameStateBuilder state, BciBlock block, boolean liveIn) { + state.clearNonLiveLocals(block, liveness, liveIn); + } + /** * @return the entry point to exception dispatch */ @@ -1346,8 +1356,7 @@ protected AbstractBeginNode handleException(ValueNode exceptionObject, int bci, assert bci == BytecodeFrame.BEFORE_BCI || bci == bci() : "invalid bci"; debug.log("Creating exception dispatch edges at %d, exception object=%s, exception seen=%s", bci, exceptionObject, (profilingInfo == null ? "" : profilingInfo.getExceptionSeen(bci))); - FrameStateBuilder dispatchState = frameState.copy(); - dispatchState.clearStack(); + FrameStateBuilder dispatchState = createFrameStateForExceptionHandling(bci); AbstractBeginNode dispatchBegin; if (exceptionObject == null) { @@ -1374,13 +1383,20 @@ protected AbstractBeginNode handleException(ValueNode exceptionObject, int bci, return dispatchBegin; } - protected void createHandleExceptionTarget(FixedWithNextNode afterExceptionLoaded, int bci, FrameStateBuilder dispatchState) { + private void createHandleExceptionTarget(FixedWithNextNode afterExceptionLoaded, int bci, FrameStateBuilder dispatchState) { FixedWithNextNode afterInstrumentation = afterExceptionLoaded; for (NodePlugin plugin : graphBuilderConfig.getPlugins().getNodePlugins()) { afterInstrumentation = plugin.instrumentExceptionDispatch(graph, afterInstrumentation, () -> dispatchState.create(bci, getNonIntrinsicAncestor(), false, null, null)); assert afterInstrumentation.next() == null : "exception dispatch instrumentation will be linked to dispatch block"; } + BciBlock dispatchBlock = getDispatchBlock(bci); + + FixedNode target = createTarget(dispatchBlock, dispatchState); + afterInstrumentation.setNext(target); + } + + protected BciBlock getDispatchBlock(int bci) { BciBlock dispatchBlock = currentBlock.exceptionDispatchBlock(); /* * The exception dispatch block is always for the last bytecode of a block, so if we are not @@ -1391,8 +1407,7 @@ protected void createHandleExceptionTarget(FixedWithNextNode afterExceptionLoade dispatchBlock = blockMap.getUnwindBlock(); } - FixedNode target = createTarget(dispatchBlock, dispatchState); - afterInstrumentation.setNext(target); + return dispatchBlock; } protected ValueNode genLoadIndexed(ValueNode array, ValueNode index, GuardingNode boundsCheck, JavaKind kind) { diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/java/FrameStateBuilder.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/java/FrameStateBuilder.java index b5ef12d95a59..2a40e463e6ec 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/java/FrameStateBuilder.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/java/FrameStateBuilder.java @@ -729,6 +729,9 @@ public boolean contains(ValueNode value) { return false; } + /** + * @param liveIn true if live in, false if live out + */ public void clearNonLiveLocals(BciBlock block, LocalLiveness liveness, boolean liveIn) { /* * Non-live local clearing is mandatory for the entry block of an OSR compilation so that diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java index 1b259fe4b770..3e96981ea34f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java @@ -25,7 +25,9 @@ package com.oracle.svm.hosted.phases; import org.graalvm.compiler.core.common.BootstrapMethodIntrospection; +import org.graalvm.compiler.java.BciBlockMapping; import org.graalvm.compiler.java.BytecodeParser; +import org.graalvm.compiler.java.FrameStateBuilder; import org.graalvm.compiler.java.GraphBuilderPhase; import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind; import org.graalvm.compiler.nodes.StructuredGraph; @@ -43,6 +45,7 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; import com.oracle.svm.util.ModuleSupport; import jdk.vm.ci.meta.JavaKind; @@ -144,5 +147,24 @@ protected void genStoreField(ValueNode receiver, ResolvedJavaField field, ValueN hostVM.recordFieldStore(field, method); super.genStoreField(receiver, field, value); } + + @Override + protected FrameStateBuilder createFrameStateForExceptionHandling(int bci) { + var dispatchState = super.createFrameStateForExceptionHandling(bci); + if (SubstrateOptions.parseOnce()) { + /* + * It is beneficial to eagerly clear all non-live locals on the exception object + * before entering the dispatch target. This helps us prune unneeded values from the + * graph, which can positively impact our analysis. Since deoptimization is not + * possible, then there is no risk in clearing the unneeded locals. + */ + AnalysisMethod aMethod = (AnalysisMethod) method; + if (aMethod.isOriginalMethod() && !SubstrateCompilationDirectives.singleton().isRegisteredForDeoptTesting(aMethod)) { + BciBlockMapping.BciBlock dispatchBlock = getDispatchBlock(bci); + clearNonLiveLocals(dispatchState, dispatchBlock, true); + } + } + return dispatchState; + } } } From 44187dd4422ba623758a000cb3d8d4172c702298 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 4 Aug 2023 13:49:01 +0200 Subject: [PATCH 31/33] Support DynamicNewInstanceNode in hightiercodegen --- .../org/graalvm/compiler/hightiercodegen/NodeLowerer.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/hightiercodegen/NodeLowerer.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/hightiercodegen/NodeLowerer.java index 5893529ff400..54a2f8dd8431 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/hightiercodegen/NodeLowerer.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/hightiercodegen/NodeLowerer.java @@ -86,6 +86,7 @@ import org.graalvm.compiler.nodes.java.AtomicReadAndWriteNode; import org.graalvm.compiler.nodes.java.ClassIsAssignableFromNode; import org.graalvm.compiler.nodes.java.DynamicNewArrayNode; +import org.graalvm.compiler.nodes.java.DynamicNewInstanceNode; import org.graalvm.compiler.nodes.java.ExceptionObjectNode; import org.graalvm.compiler.nodes.java.InstanceOfDynamicNode; import org.graalvm.compiler.nodes.java.InstanceOfNode; @@ -333,6 +334,8 @@ protected void dispatch(Node node) { lower((IdentityHashCodeNode) node); } else if (node instanceof ClassIsAssignableFromNode) { lower((ClassIsAssignableFromNode) node); + } else if (node instanceof DynamicNewInstanceNode n) { + lower(n); } else { if (!isIgnored(node)) { handleUnknownNodeType(node); @@ -439,6 +442,8 @@ protected void handleUnknownNodeType(Node node) { protected abstract void lower(NewInstanceNode node); + protected abstract void lower(DynamicNewInstanceNode node); + protected abstract void lower(NotNode node); protected abstract void lower(NarrowNode node); From 8bd168ee6f2603f3a9a45e183f28b29e1491a614 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Fri, 11 Aug 2023 14:24:30 +0200 Subject: [PATCH 32/33] Stable names for direct method handle invokers. --- .../compiler/replacements/PEGraphDecoder.java | 2 +- substratevm/mx.substratevm/testhello.py | 4 +- .../annotation/CustomSubstitutionType.java | 385 ++++++++++++++++++ .../hosted/lambda/LambdaSubstitutionType.java | 351 +--------------- .../methodhandles/MethodHandleFeature.java | 12 + ...eInvokerRenamingSubstitutionProcessor.java | 140 +++++++ .../MethodHandleInvokerSubstitutionType.java | 43 ++ .../InlineBeforeAnalysisPolicyUtils.java | 7 +- 8 files changed, 591 insertions(+), 353 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/CustomSubstitutionType.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerRenamingSubstitutionProcessor.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerSubstitutionType.java diff --git a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java index 9547464002d0..d6b0891525c6 100644 --- a/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java +++ b/compiler/src/jdk.internal.vm.compiler/src/org/graalvm/compiler/replacements/PEGraphDecoder.java @@ -1694,7 +1694,7 @@ protected void ensureOuterStateDecoded(PEMethodScope methodScope) { /** * Determines whether to omit an intermediate method (a method other than the root method or a - * leaf callee) from {@link FrameState} or {@link NodeSourcePosition} information. When used to + * leaf caller) from {@link FrameState} or {@link NodeSourcePosition} information. When used to * discard intermediate methods of generated code with non-deterministic names, for example, * this can improve matching of profile-guided optimization information between executions. */ diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/mx.substratevm/testhello.py index a7b0ecce0c71..a5b5f829b12c 100644 --- a/substratevm/mx.substratevm/testhello.py +++ b/substratevm/mx.substratevm/testhello.py @@ -124,7 +124,7 @@ def test(): # expect "#1 0x[0-9a-f]+ in com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_.* at [a-z/]+/JavaMainWrapper.java:[0-9]+" exec_string = execute("backtrace") stacktraceRegex = [r"#0%shello\.Hello::main%s %s at hello/Hello\.java:%d"%(spaces_pattern, param_types_pattern, arg_values_pattern, main_start), - r"#1%s(%s in )?java\.lang\.invoke\.LambdaForm\$DMH/0x%s::invokeStatic(Init)?%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), + r"#1%s(%s in )?java\.lang\.invoke\.LambdaForm\$DMH/s%s::invokeStatic(Init)?%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), r"#2%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), r"#3%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), r"#4%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), @@ -370,7 +370,7 @@ def test(): exec_string = execute("backtrace") stacktraceRegex = [r"#0%shello\.Hello\$Greeter::greeter%s %s at hello/Hello\.java:38"%(spaces_pattern, param_types_pattern, arg_values_pattern), r"#1%s%s in hello\.Hello::main%s %s at hello/Hello\.java:%d"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, main_start), - r"#2%s(%s in )?java\.lang\.invoke\.LambdaForm\$DMH/0x%s::invokeStatic(Init)?%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), + r"#2%s(%s in )?java\.lang\.invoke\.LambdaForm\$DMH/s%s::invokeStatic(Init)?%s %s( at java/lang/invoke/%s:[0-9]+)?"%(spaces_pattern, address_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern, package_file_pattern), r"#3%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern), r"#4%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), r"#5%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/CustomSubstitutionType.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/CustomSubstitutionType.java new file mode 100644 index 000000000000..379fc0a06a41 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/CustomSubstitutionType.java @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2023, 2023, 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.hosted.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; + +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; +import com.oracle.svm.core.util.VMError; + +import jdk.vm.ci.meta.Assumptions.AssumptionResult; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.Signature; +import jdk.vm.ci.meta.UnresolvedJavaField; +import jdk.vm.ci.meta.UnresolvedJavaType; + +public abstract class CustomSubstitutionType implements ResolvedJavaType, OriginalClassProvider, AnnotationWrapper { + private final ResolvedJavaType original; + + public CustomSubstitutionType(ResolvedJavaType original) { + this.original = original; + } + + @Override + public String getName() { + return original.getName(); + } + + @Override + public AnnotatedElement getAnnotationRoot() { + return null; + } + + @Override + public boolean hasFinalizer() { + return original.hasFinalizer(); + } + + @Override + public AssumptionResult hasFinalizableSubclass() { + return original.hasFinalizableSubclass(); + } + + @Override + public boolean isInterface() { + return original.isInterface(); + } + + @Override + public boolean isInstanceClass() { + return original.isInstanceClass(); + } + + @Override + public boolean isPrimitive() { + return original.isPrimitive(); + } + + @Override + public boolean isLeaf() { + return original.isLeaf(); + } + + @Override + public boolean isEnum() { + return original.isEnum(); + } + + @Override + public boolean isInitialized() { + return original.isInitialized(); + } + + @Override + public void initialize() { + original.initialize(); + } + + @Override + public boolean isLinked() { + return original.isLinked(); + } + + @Override + public void link() { + original.link(); + } + + @Override + public boolean hasDefaultMethods() { + return original.hasDefaultMethods(); + } + + @Override + public boolean declaresDefaultMethods() { + return original.declaresDefaultMethods(); + } + + @Override + public boolean isAssignableFrom(ResolvedJavaType other) { + return original.isAssignableFrom(other); + } + + @SuppressWarnings("deprecation") + @Override + public ResolvedJavaType getHostClass() { + return original.getHostClass(); + } + + @Override + public boolean isJavaLangObject() { + return original.isJavaLangObject(); + } + + @Override + public boolean isInstance(JavaConstant obj) { + return original.isInstance(obj); + } + + @Override + public ResolvedJavaType getSuperclass() { + return original.getSuperclass(); + } + + @Override + public ResolvedJavaType[] getInterfaces() { + return original.getInterfaces(); + } + + @Override + public ResolvedJavaType getSingleImplementor() { + return original.getSingleImplementor(); + } + + @Override + public ResolvedJavaType findLeastCommonAncestor(ResolvedJavaType otherType) { + return original.findLeastCommonAncestor(otherType); + } + + @Override + public AssumptionResult findLeafConcreteSubtype() { + return original.findLeafConcreteSubtype(); + } + + @Override + public ResolvedJavaType getComponentType() { + return original.getComponentType(); + } + + @Override + public ResolvedJavaType getElementalType() { + return original.getElementalType(); + } + + @Override + public ResolvedJavaType getArrayClass() { + return original.getArrayClass(); + } + + @Override + public ResolvedJavaMethod resolveMethod(ResolvedJavaMethod method, ResolvedJavaType callerType) { + return original.resolveMethod(method, callerType); + } + + @Override + public ResolvedJavaMethod resolveConcreteMethod(ResolvedJavaMethod method, ResolvedJavaType callerType) { + return original.resolveConcreteMethod(method, callerType); + } + + @Override + public AssumptionResult findUniqueConcreteMethod(ResolvedJavaMethod method) { + return original.findUniqueConcreteMethod(method); + } + + @Override + public ResolvedJavaField[] getInstanceFields(boolean includeSuperclasses) { + return original.getInstanceFields(includeSuperclasses); + } + + @Override + public ResolvedJavaField[] getStaticFields() { + return original.getStaticFields(); + } + + @Override + public ResolvedJavaField findInstanceFieldWithOffset(long offset, JavaKind expectedKind) { + return original.findInstanceFieldWithOffset(offset, expectedKind); + } + + @Override + public String getSourceFileName() { + return original.getSourceFileName(); + } + + @Override + public boolean isLocal() { + return original.isLocal(); + } + + @Override + public boolean isMember() { + return original.isMember(); + } + + @Override + public ResolvedJavaType getEnclosingType() { + return original.getEnclosingType(); + } + + @Override + public ResolvedJavaMethod[] getDeclaredConstructors() { + return getDeclaredConstructors(true); + } + + @Override + public ResolvedJavaMethod[] getDeclaredConstructors(boolean forceLink) { + VMError.guarantee(forceLink == false, "only use getDeclaredConstructors without forcing to link, because linking can throw LinkageError"); + return original.getDeclaredConstructors(forceLink); + } + + @Override + public ResolvedJavaMethod[] getDeclaredMethods() { + return getDeclaredMethods(true); + } + + @Override + public ResolvedJavaMethod[] getDeclaredMethods(boolean forceLink) { + return original.getDeclaredMethods(forceLink); + } + + @Override + public ResolvedJavaMethod getClassInitializer() { + return original.getClassInitializer(); + } + + @Override + public ResolvedJavaMethod findMethod(String name, Signature signature) { + return original.findMethod(name, signature); + } + + @Override + public boolean isCloneableWithAllocation() { + return original.isCloneableWithAllocation(); + } + + @Override + public ResolvedJavaType lookupType(UnresolvedJavaType unresolvedJavaType, boolean resolve) { + return original.lookupType(unresolvedJavaType, resolve); + } + + @Override + public ResolvedJavaField resolveField(UnresolvedJavaField unresolvedJavaField, ResolvedJavaType accessingClass) { + return original.resolveField(unresolvedJavaField, accessingClass); + } + + @Override + public boolean isArray() { + return original.isArray(); + } + + @Override + public JavaKind getJavaKind() { + return original.getJavaKind(); + } + + @Override + public ResolvedJavaType resolve(ResolvedJavaType accessingClass) { + return original.resolve(accessingClass); + } + + @Override + public int getModifiers() { + return original.getModifiers(); + } + + @Override + public boolean isSynchronized() { + return original.isSynchronized(); + } + + @Override + public boolean isStatic() { + return original.isStatic(); + } + + @Override + public boolean isFinalFlagSet() { + return original.isFinalFlagSet(); + } + + @Override + public boolean isPublic() { + return original.isPublic(); + } + + @Override + public boolean isPackagePrivate() { + return original.isPackagePrivate(); + } + + @Override + public boolean isPrivate() { + return original.isPrivate(); + } + + @Override + public boolean isProtected() { + return original.isProtected(); + } + + @Override + public boolean isTransient() { + return original.isTransient(); + } + + @Override + public boolean isStrict() { + return original.isStrict(); + } + + @Override + public boolean isVolatile() { + return original.isVolatile(); + } + + @Override + public boolean isNative() { + return original.isNative(); + } + + @Override + public boolean isAbstract() { + return original.isAbstract(); + } + + @Override + public boolean isConcrete() { + return original.isConcrete(); + } + + @Override + public T[] getAnnotationsByType(Class annotationClass) { + return original.getAnnotationsByType(annotationClass); + } + + @Override + public T[] getDeclaredAnnotationsByType(Class annotationClass) { + return original.getDeclaredAnnotationsByType(annotationClass); + } + + public ResolvedJavaType getOriginal() { + return original; + } + + @Override + public Class getJavaClass() { + return OriginalClassProvider.getJavaClass(original); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaSubstitutionType.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaSubstitutionType.java index 4b6b04fb71c1..c68d490f4442 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaSubstitutionType.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/lambda/LambdaSubstitutionType.java @@ -24,36 +24,22 @@ */ package com.oracle.svm.hosted.lambda; -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; - -import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; import com.oracle.svm.core.jdk.LambdaFormHiddenMethod; -import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.annotation.AnnotationValue; -import com.oracle.svm.hosted.annotation.AnnotationWrapper; +import com.oracle.svm.hosted.annotation.CustomSubstitutionType; import com.oracle.svm.hosted.annotation.SubstrateAnnotationExtractor; -import jdk.vm.ci.meta.Assumptions.AssumptionResult; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.ResolvedJavaField; -import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; -import jdk.vm.ci.meta.Signature; -import jdk.vm.ci.meta.UnresolvedJavaField; -import jdk.vm.ci.meta.UnresolvedJavaType; /** * Simply changes the name of Lambdas from a random ID into a stable name. */ -public class LambdaSubstitutionType implements ResolvedJavaType, OriginalClassProvider, AnnotationWrapper { - private final ResolvedJavaType original; +public class LambdaSubstitutionType extends CustomSubstitutionType { private final String stableName; @SuppressWarnings("try") LambdaSubstitutionType(ResolvedJavaType original, String stableName) { - this.original = original; + super(original); this.stableName = stableName; } @@ -62,341 +48,10 @@ public String getName() { return stableName; } - @Override - public AnnotatedElement getAnnotationRoot() { - return null; - } - private static final AnnotationValue[] INJECTED_ANNOTATIONS = SubstrateAnnotationExtractor.prepareInjectedAnnotations(LambdaFormHiddenMethod.Holder.INSTANCE); @Override public AnnotationValue[] getInjectedAnnotations() { return INJECTED_ANNOTATIONS; } - - @Override - public boolean hasFinalizer() { - return original.hasFinalizer(); - } - - @Override - public AssumptionResult hasFinalizableSubclass() { - return original.hasFinalizableSubclass(); - } - - @Override - public boolean isInterface() { - return original.isInterface(); - } - - @Override - public boolean isInstanceClass() { - return original.isInstanceClass(); - } - - @Override - public boolean isPrimitive() { - return original.isPrimitive(); - } - - @Override - public boolean isLeaf() { - return original.isLeaf(); - } - - @Override - public boolean isEnum() { - return original.isEnum(); - } - - @Override - public boolean isInitialized() { - return original.isInitialized(); - } - - @Override - public void initialize() { - original.initialize(); - } - - @Override - public boolean isLinked() { - return original.isLinked(); - } - - @Override - public void link() { - original.link(); - } - - @Override - public boolean hasDefaultMethods() { - return original.hasDefaultMethods(); - } - - @Override - public boolean declaresDefaultMethods() { - return original.declaresDefaultMethods(); - } - - @Override - public boolean isAssignableFrom(ResolvedJavaType other) { - return original.isAssignableFrom(other); - } - - @SuppressWarnings("deprecation") - @Override - public ResolvedJavaType getHostClass() { - return original.getHostClass(); - } - - @Override - public boolean isJavaLangObject() { - return original.isJavaLangObject(); - } - - @Override - public boolean isInstance(JavaConstant obj) { - return original.isInstance(obj); - } - - @Override - public ResolvedJavaType getSuperclass() { - return original.getSuperclass(); - } - - @Override - public ResolvedJavaType[] getInterfaces() { - return original.getInterfaces(); - } - - @Override - public ResolvedJavaType getSingleImplementor() { - return original.getSingleImplementor(); - } - - @Override - public ResolvedJavaType findLeastCommonAncestor(ResolvedJavaType otherType) { - return original.findLeastCommonAncestor(otherType); - } - - @Override - public AssumptionResult findLeafConcreteSubtype() { - return original.findLeafConcreteSubtype(); - } - - @Override - public ResolvedJavaType getComponentType() { - return original.getComponentType(); - } - - @Override - public ResolvedJavaType getElementalType() { - return original.getElementalType(); - } - - @Override - public ResolvedJavaType getArrayClass() { - return original.getArrayClass(); - } - - @Override - public ResolvedJavaMethod resolveMethod(ResolvedJavaMethod method, ResolvedJavaType callerType) { - return original.resolveMethod(method, callerType); - } - - @Override - public ResolvedJavaMethod resolveConcreteMethod(ResolvedJavaMethod method, ResolvedJavaType callerType) { - return original.resolveConcreteMethod(method, callerType); - } - - @Override - public AssumptionResult findUniqueConcreteMethod(ResolvedJavaMethod method) { - return original.findUniqueConcreteMethod(method); - } - - @Override - public ResolvedJavaField[] getInstanceFields(boolean includeSuperclasses) { - return original.getInstanceFields(includeSuperclasses); - } - - @Override - public ResolvedJavaField[] getStaticFields() { - return original.getStaticFields(); - } - - @Override - public ResolvedJavaField findInstanceFieldWithOffset(long offset, JavaKind expectedKind) { - return original.findInstanceFieldWithOffset(offset, expectedKind); - } - - @Override - public String getSourceFileName() { - return original.getSourceFileName(); - } - - @Override - public boolean isLocal() { - return original.isLocal(); - } - - @Override - public boolean isMember() { - return original.isMember(); - } - - @Override - public ResolvedJavaType getEnclosingType() { - return original.getEnclosingType(); - } - - @Override - public ResolvedJavaMethod[] getDeclaredConstructors() { - return getDeclaredConstructors(true); - } - - @Override - public ResolvedJavaMethod[] getDeclaredConstructors(boolean forceLink) { - VMError.guarantee(forceLink == false, "only use getDeclaredConstructors without forcing to link, because linking can throw LinkageError"); - return original.getDeclaredConstructors(forceLink); - } - - @Override - public ResolvedJavaMethod[] getDeclaredMethods() { - return getDeclaredMethods(true); - } - - @Override - public ResolvedJavaMethod[] getDeclaredMethods(boolean forceLink) { - return original.getDeclaredMethods(forceLink); - } - - @Override - public ResolvedJavaMethod getClassInitializer() { - return original.getClassInitializer(); - } - - @Override - public ResolvedJavaMethod findMethod(String name, Signature signature) { - return original.findMethod(name, signature); - } - - @Override - public boolean isCloneableWithAllocation() { - return original.isCloneableWithAllocation(); - } - - @Override - public ResolvedJavaType lookupType(UnresolvedJavaType unresolvedJavaType, boolean resolve) { - return original.lookupType(unresolvedJavaType, resolve); - } - - @Override - public ResolvedJavaField resolveField(UnresolvedJavaField unresolvedJavaField, ResolvedJavaType accessingClass) { - return original.resolveField(unresolvedJavaField, accessingClass); - } - - @Override - public boolean isArray() { - return original.isArray(); - } - - @Override - public JavaKind getJavaKind() { - return original.getJavaKind(); - } - - @Override - public ResolvedJavaType resolve(ResolvedJavaType accessingClass) { - return original.resolve(accessingClass); - } - - @Override - public int getModifiers() { - return original.getModifiers(); - } - - @Override - public boolean isSynchronized() { - return original.isSynchronized(); - } - - @Override - public boolean isStatic() { - return original.isStatic(); - } - - @Override - public boolean isFinalFlagSet() { - return original.isFinalFlagSet(); - } - - @Override - public boolean isPublic() { - return original.isPublic(); - } - - @Override - public boolean isPackagePrivate() { - return original.isPackagePrivate(); - } - - @Override - public boolean isPrivate() { - return original.isPrivate(); - } - - @Override - public boolean isProtected() { - return original.isProtected(); - } - - @Override - public boolean isTransient() { - return original.isTransient(); - } - - @Override - public boolean isStrict() { - return original.isStrict(); - } - - @Override - public boolean isVolatile() { - return original.isVolatile(); - } - - @Override - public boolean isNative() { - return original.isNative(); - } - - @Override - public boolean isAbstract() { - return original.isAbstract(); - } - - @Override - public boolean isConcrete() { - return original.isConcrete(); - } - - @Override - public T[] getAnnotationsByType(Class annotationClass) { - return original.getAnnotationsByType(annotationClass); - } - - @Override - public T[] getDeclaredAnnotationsByType(Class annotationClass) { - return original.getDeclaredAnnotationsByType(annotationClass); - } - - public ResolvedJavaType getOriginal() { - return original; - } - - @Override - public Class getJavaClass() { - return OriginalClassProvider.getJavaClass(original); - } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java index 6434dea55452..c366559db82f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleFeature.java @@ -47,6 +47,7 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.util.ReflectionUtil; import sun.invoke.util.ValueConversions; @@ -101,6 +102,8 @@ public class MethodHandleFeature implements InternalFeature { private Object runtimeMethodTypeInternTable; private Method concurrentWeakInternSetAdd; + private MethodHandleInvokerRenamingSubstitutionProcessor substitutionProcessor; + @Override public void duringSetup(DuringSetupAccess access) { Class memberNameClass = access.findClassByName("java.lang.invoke.MemberName"); @@ -121,6 +124,10 @@ public void duringSetup(DuringSetupAccess access) { Class concurrentWeakInternSetClass = access.findClassByName("java.lang.invoke.MethodType$ConcurrentWeakInternSet"); runtimeMethodTypeInternTable = ReflectionUtil.newInstance(concurrentWeakInternSetClass); concurrentWeakInternSetAdd = ReflectionUtil.lookupMethod(concurrentWeakInternSetClass, "add", Object.class); + + var accessImpl = (DuringSetupAccessImpl) access; + substitutionProcessor = new MethodHandleInvokerRenamingSubstitutionProcessor(accessImpl.getBigBang()); + accessImpl.registerSubstitutionProcessor(substitutionProcessor); } @Override @@ -381,4 +388,9 @@ private static void scanBoundMethodHandle(DuringAnalysisAccess a, Class bmhSu access.requireAnalysisIteration(); } } + + @Override + public void afterAnalysis(AfterAnalysisAccess access) { + assert substitutionProcessor.checkAllTypeNames(); + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerRenamingSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerRenamingSubstitutionProcessor.java new file mode 100644 index 000000000000..ac025a32f451 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerRenamingSubstitutionProcessor.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2023, 2023, 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.hosted.methodhandles; + +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; +import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * A substitution processor that renames classes generated by {@code InvokerBytecodeGenerator}, + * which are assigned more or less arbitrary names by the host VM, to stable names that are based on + * the {@code LambdaForm} which they were compiled from. + */ +public class MethodHandleInvokerRenamingSubstitutionProcessor extends SubstitutionProcessor { + private static final Class LAMBDA_FORM_CLASS = ReflectionUtil.lookupClass(false, "java.lang.invoke.LambdaForm"); + private static final Method CLASS_GET_CLASS_DATA_METHOD = ReflectionUtil.lookupMethod(Class.class, "getClassData"); + + /* + * We currently only replace the invokers of direct method handles which have a simpler + * structure and appear to be reliably reused. + */ + private static final String CLASS_NAME_SUBSTRING = "LambdaForm$DMH"; + private static final String STABLE_NAME_TEMPLATE = "Ljava/lang/invoke/LambdaForm$DMH.s"; + + private final BigBang bb; + + private final ConcurrentMap typeSubstitutions = new ConcurrentHashMap<>(); + private final Set uniqueTypeNames = new HashSet<>(); + + MethodHandleInvokerRenamingSubstitutionProcessor(BigBang bb) { + this.bb = bb; + } + + @Override + public ResolvedJavaType lookup(ResolvedJavaType type) { + if (!shouldReplace(type)) { + return type; + } + return getSubstitution(type); + } + + private static boolean shouldReplace(ResolvedJavaType type) { + return !(type instanceof MethodHandleInvokerSubstitutionType) && type.isFinalFlagSet() && type.getName().contains(CLASS_NAME_SUBSTRING); + } + + @Override + public ResolvedJavaType resolve(ResolvedJavaType type) { + if (type instanceof MethodHandleInvokerSubstitutionType) { + return ((MethodHandleInvokerSubstitutionType) type).getOriginal(); + } + return type; + } + + private ResolvedJavaType getSubstitution(ResolvedJavaType type) { + return typeSubstitutions.computeIfAbsent(type, original -> { + try { + Class clazz = OriginalClassProvider.getJavaClass(original); + Object classData = CLASS_GET_CLASS_DATA_METHOD.invoke(clazz); + VMError.guarantee(LAMBDA_FORM_CLASS.isInstance(classData)); + int hash = classData.hashCode(); + return new MethodHandleInvokerSubstitutionType(original, findUniqueName(hash)); + } catch (ReflectiveOperationException e) { + throw VMError.shouldNotReachHere(e); + } + }); + } + + private String findUniqueName(int hashCode) { + CharSequence baseName = STABLE_NAME_TEMPLATE + Integer.toHexString(hashCode); + String name = baseName + ";"; + synchronized (uniqueTypeNames) { + int suffix = 1; + while (uniqueTypeNames.contains(name)) { + name = baseName + "_" + suffix + ";"; + suffix++; + } + uniqueTypeNames.add(name); + return name; + } + } + + boolean checkAllTypeNames() { + if (!SubstrateUtil.assertionsEnabled()) { + throw new AssertionError("Expensive check: should only run with assertions enabled."); + } + + List types = bb.getUniverse().getTypes(); + + if (types.stream().anyMatch(aType -> shouldReplace(aType.getWrapped()))) { + throw new AssertionError("All relevant types must have been substituted."); + } + + Set names = new HashSet<>(); + types.stream() + .map(AnalysisType::getName) + .filter(x -> x.contains(CLASS_NAME_SUBSTRING)) + .forEach(name -> { + if (names.contains(name)) { + throw new AssertionError("Duplicate name: " + name); + } + names.add(name); + }); + return true; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerSubstitutionType.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerSubstitutionType.java new file mode 100644 index 000000000000..1b8158ee6f9a --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/methodhandles/MethodHandleInvokerSubstitutionType.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023, 2023, 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.hosted.methodhandles; + +import com.oracle.svm.hosted.annotation.CustomSubstitutionType; + +import jdk.vm.ci.meta.ResolvedJavaType; + +class MethodHandleInvokerSubstitutionType extends CustomSubstitutionType { + private final String stableName; + + MethodHandleInvokerSubstitutionType(ResolvedJavaType original, String stableName) { + super(original); + this.stableName = stableName; + } + + @Override + public String getName() { + return stableName; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java index 332718ea10c9..1ad826a3effe 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisPolicyUtils.java @@ -70,6 +70,7 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ReachabilityRegistrationNode; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.methodhandles.MethodHandleInvokerRenamingSubstitutionProcessor; import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.ResolvedJavaMethod; @@ -486,9 +487,11 @@ private static boolean inlineForMethodHandleIntrinsification(ResolvedJavaMethod } /** - * Discard information on inlined calls to generated classes of LambdaForms, which are not - * assigned names that are stable between executions and would cause mismatches in collected + * Discard information on inlined calls to generated classes of LambdaForms, not all of which + * are assigned names that are stable between executions and would cause mismatches in collected * profile-guided optimization data which prevent optimizations. + * + * @see MethodHandleInvokerRenamingSubstitutionProcessor */ protected boolean shouldOmitIntermediateMethodInState(ResolvedJavaMethod method) { return method.isAnnotationPresent(COMPILED_LAMBDA_FORM_ANNOTATION); From b497acf3933e790d6e670cc44c02fa37b07cfd71 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Fri, 11 Aug 2023 16:31:41 +0200 Subject: [PATCH 33/33] Address various debuginfotest issues. --- substratevm/mx.substratevm/testhello.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/mx.substratevm/testhello.py index a5b5f829b12c..6cccd403e3d8 100644 --- a/substratevm/mx.substratevm/testhello.py +++ b/substratevm/mx.substratevm/testhello.py @@ -154,7 +154,7 @@ def test(): rexp = [r"%s = {"%(wildcard_pattern), r"%s = {"%(spaces_pattern), r"%s<_objhdr> = {"%(spaces_pattern), - r"%shub = %s,"%(spaces_pattern, address_pattern), + r"%shub = %s"%(spaces_pattern, address_pattern), r"%sidHash = %s"%(spaces_pattern, address_pattern) if fixed_idhash_field else None, r"%s}, }, "%(spaces_pattern), r"%smembers of java\.lang\.String\[\]:"%(spaces_pattern), @@ -173,7 +173,7 @@ def test(): r"%s = {"%(spaces_pattern), r"%s = {"%(spaces_pattern), r"%s<_objhdr> = {"%(spaces_pattern), - r"%shub = %s,"%(spaces_pattern, address_pattern), + r"%shub = %s"%(spaces_pattern, address_pattern), r"%sidHash = %s"%(spaces_pattern, address_pattern) if fixed_idhash_field else None, r"%s}, },"%(spaces_pattern), r"%smembers of java\.lang\.Class:"%(spaces_pattern), @@ -183,7 +183,7 @@ def test(): rexp = [r"%s = {"%(wildcard_pattern), r"%s = {"%(spaces_pattern), r"%s<_objhdr> = {"%(spaces_pattern), - r"%shub = %s,"%(spaces_pattern, address_pattern), + r"%shub = %s"%(spaces_pattern, address_pattern), r"%sidHash = %s"%(spaces_pattern, address_pattern) if fixed_idhash_field else None, r"%s}, },"%(spaces_pattern), r"%smembers of java\.lang\.Class:"%(spaces_pattern), @@ -223,7 +223,7 @@ def test(): rexp = [r"%s = {"%(wildcard_pattern), r"%s = {"%(spaces_pattern), r"%s<_objhdr> = {"%(spaces_pattern), - r"%shub = %s,"%(spaces_pattern, address_pattern), + r"%shub = %s"%(spaces_pattern, address_pattern), r"%sidHash = %s"%(spaces_pattern, address_pattern) if fixed_idhash_field else None, r"%s}, },"%(spaces_pattern), r"%smembers of java\.lang\.Class:"%(spaces_pattern), @@ -236,21 +236,21 @@ def test(): # ensure we can access fields of class constants exec_string = execute("print 'java.lang.String[].class'.name->value->data") - rexp = r'%s = %s "\[Ljava.lang.String;"'%(wildcard_pattern, address_pattern) + rexp = r'%s = %s "\[Ljava.lang.String;'%(wildcard_pattern, address_pattern) checker = Checker("print 'java.lang.String[].class'.name->value->data", rexp) checker.check(exec_string) exec_string = execute("print 'long.class'.name->value->data") - rexp = r'%s = %s "long"'%(wildcard_pattern, address_pattern) + rexp = r'%s = %s "long'%(wildcard_pattern, address_pattern) checker = Checker("print 'long.class'.name->value->data", rexp) checker.check(exec_string) exec_string = execute("print 'byte[].class'.name->value->data") - rexp = r'%s = %s "\[B"'%(wildcard_pattern, address_pattern) + rexp = r'%s = %s "\[B'%(wildcard_pattern, address_pattern) checker = Checker("print 'byte[].class'.name->value->data", rexp)