From 44d6447361cfa9355147859cb36a37117fb231fd Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Sat, 27 Sep 2025 19:24:06 +0200 Subject: [PATCH 01/14] When crema is enabled, don't add digest to lambda names --- .../svm/core/hub/registry/AbstractRuntimeClassRegistry.java | 2 +- .../com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java index ba5fd4246cbb..3283fe7baf25 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java @@ -201,7 +201,7 @@ private Class defineClassInner(Symbol typeOrNull, byte[] b, int off, in } ParserKlass parsed = parseClass(typeOrNull, info, data); Symbol type = typeOrNull == null ? parsed.getType() : typeOrNull; - assert typeOrNull == null || type == parsed.getType(); + assert typeOrNull == null || type == parsed.getType() : typeOrNull + " vs. " + parsed.getType(); if (info.addedToRegistry() && findLoadedClass(type) != null) { String kind; if (Modifier.isInterface(parsed.getFlags())) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java index f11ebe7b9880..c7cde95441ea 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java @@ -353,7 +353,8 @@ private static Class defineClass0(ClassLoader loader, Class lookup, String @SuppressWarnings("unused") boolean initialize, int flags, Object classData) { // Note that if name is not null, it is a binary name in either / or .-form String actualName = name; - if (LambdaUtils.isLambdaClassName(name)) { + assert !(PredefinedClassesSupport.hasBytecodeClasses() && RuntimeClassLoading.isSupported()); + if (!RuntimeClassLoading.isSupported() && LambdaUtils.isLambdaClassName(name)) { actualName += Digest.digest(b); } boolean isNestMate = (flags & ClassLoaderHelper.NESTMATE_CLASS) != 0; From b11aceb40dac21ca890bb9a599e7018953d199c8 Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Sun, 28 Sep 2025 16:43:10 +0200 Subject: [PATCH 02/14] Crema: add resolution for INVOKEDYNAMIC entries In the `RuntimeInterpreterConstantPool`, INVOKEDYNAMIC entries are resolved to a `ResolvedInvokeDynamicConstant`. Those hold information about the bootstrap method and a list of call site links. Call site links can either be successful or failed. Successful call site links contain an invoker method as well as an appendix. The index of the call site link is patched into the 2 empty bytes of the 4 bytes CPI of invokedynamic bytecodes. --- ...invoke_BootstrapMethodInvoker_VM_BSCI.java | 64 +++++++ ..._java_lang_invoke_MethodHandleNatives.java | 16 +- ...java_lang_invoke_MethodHandles_Lookup.java | 9 +- .../interpreter/metadata/BytecodeStream.java | 22 +++ .../metadata/CremaMethodAccess.java | 36 ++++ .../metadata/CremaResolvedObjectType.java | 5 + .../InterpreterResolvedJavaMethod.java | 3 +- .../oracle/svm/interpreter/CallSiteLink.java | 36 ++++ .../svm/interpreter/FailedCallSiteLink.java | 51 +++++ .../oracle/svm/interpreter/Interpreter.java | 72 ++++++-- .../ResolvedInvokeDynamicConstant.java | 174 ++++++++++++++++++ .../RuntimeInterpreterConstantPool.java | 32 ++++ .../interpreter/SuccessfulCallSiteLink.java | 59 ++++++ 13 files changed, 553 insertions(+), 26 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_BootstrapMethodInvoker_VM_BSCI.java create mode 100644 substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CallSiteLink.java create mode 100644 substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/FailedCallSiteLink.java create mode 100644 substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ResolvedInvokeDynamicConstant.java create mode 100644 substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/SuccessfulCallSiteLink.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_BootstrapMethodInvoker_VM_BSCI.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_BootstrapMethodInvoker_VM_BSCI.java new file mode 100644 index 000000000000..7e42f59d9efc --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_BootstrapMethodInvoker_VM_BSCI.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.methodhandles; + +import static com.oracle.svm.core.util.VMError.unsupportedFeature; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.hub.RuntimeClassLoading.WithRuntimeClassLoading; +import com.oracle.svm.core.util.BasedOnJDKFile; + +/** + * This is used in a special case of {@code BootstrapMethodInvoker.invoke} (See + * {@code @BasedOnJDKFile} annotation on this class) which is not currently used in SVM. + *

+ * These substitutions cut paths that would lead to useless code being included and some deleted + * methods being reached. + */ +@TargetClass(className = "java.lang.invoke.BootstrapMethodInvoker", innerClass = "VM_BSCI", onlyWith = WithRuntimeClassLoading.class) +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+36/src/java.base/share/classes/java/lang/invoke/BootstrapMethodInvoker.java#L113-L126") +final class Target_java_lang_invoke_BootstrapMethodInvoker_VM_BSCI { + @Substitute + @SuppressWarnings("unused") + Target_java_lang_invoke_BootstrapMethodInvoker_VM_BSCI(MethodHandle bsm, String name, Object type, MethodHandles.Lookup lookup, int[] indexInfo) { + throw unsupportedFeature("BootstrapMethodInvoker$VM_BSCI"); + } + + @Substitute + @SuppressWarnings({"unused", "static-method"}) + Object fillCache(int i) { + throw unsupportedFeature("BootstrapMethodInvoker$VM_BSCI.fillCache"); + } + + @Substitute + @SuppressWarnings({"unused", "static-method"}) + public int copyConstants(int start, int end, Object[] buf, int pos) { + throw unsupportedFeature("BootstrapMethodInvoker$VM_BSCI.copyConstants"); + } +} 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 9d778fb6709d..7318012b91d2 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 @@ -52,6 +52,8 @@ import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.RuntimeClassLoading; +import com.oracle.svm.core.hub.RuntimeClassLoading.NoRuntimeClassLoading; +import com.oracle.svm.core.hub.RuntimeClassLoading.WithRuntimeClassLoading; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; @@ -219,15 +221,23 @@ public static Target_java_lang_invoke_MemberName resolve(Target_java_lang_invoke } @Alias - @TargetElement(onlyWith = RuntimeClassLoading.WithRuntimeClassLoading.class) + @TargetElement(onlyWith = WithRuntimeClassLoading.class) + public static native Target_java_lang_invoke_MemberName linkCallSite(Object callerObj, + Object bootstrapMethodObj, + Object nameObj, Object typeObj, + Object staticArguments, + Object[] appendixResult); + + @Alias + @TargetElement(onlyWith = WithRuntimeClassLoading.class) public static native MethodType findMethodHandleType(Class rtype, Class[] ptypes); @Delete - @TargetElement(onlyWith = RuntimeClassLoading.NoRuntimeClassLoading.class, name = "linkMethodHandleConstant") + @TargetElement(onlyWith = NoRuntimeClassLoading.class, name = "linkMethodHandleConstant") static native MethodHandle linkMethodHandleConstantDeleted(Class callerClass, int refKind, Class defc, String name, Object type); @Alias - @TargetElement(onlyWith = RuntimeClassLoading.WithRuntimeClassLoading.class) + @TargetElement(onlyWith = WithRuntimeClassLoading.class) public static native MethodHandle linkMethodHandleConstant(Class callerClass, int refKind, Class defc, String name, Object type); } 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 7c3259eb21ce..2fb745b83817 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 @@ -37,19 +37,20 @@ import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.hub.RuntimeClassLoading; +import com.oracle.svm.core.hub.RuntimeClassLoading.NoRuntimeClassLoading; +import com.oracle.svm.core.hub.RuntimeClassLoading.WithRuntimeClassLoading; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; @TargetClass(value = MethodHandles.class, innerClass = "Lookup") final class Target_java_lang_invoke_MethodHandles_Lookup { // Checkstyle: stop @Delete // - @TargetElement(onlyWith = RuntimeClassLoading.NoRuntimeClassLoading.class, name = "LOOKASIDE_TABLE") // + @TargetElement(onlyWith = NoRuntimeClassLoading.class, name = "LOOKASIDE_TABLE") // static ConcurrentHashMap LOOKASIDE_TABLE_DELETED; @Alias // @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClass = ConcurrentHashMap.class)// - @TargetElement(onlyWith = RuntimeClassLoading.WithRuntimeClassLoading.class) // + @TargetElement(onlyWith = WithRuntimeClassLoading.class) // static ConcurrentHashMap LOOKASIDE_TABLE; // Checkstyle: resume @@ -95,6 +96,6 @@ private IllegalAccessException makeAccessException(Class targetClass) { } @Delete - @TargetElement(onlyWith = RuntimeClassLoading.NoRuntimeClassLoading.class) // + @TargetElement(onlyWith = NoRuntimeClassLoading.class) // native MethodHandle linkMethodHandleConstant(byte refKind, Class defc, String name, Object type) throws ReflectiveOperationException; } diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/BytecodeStream.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/BytecodeStream.java index cfb888790ab9..fea18e449505 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/BytecodeStream.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/BytecodeStream.java @@ -260,6 +260,16 @@ public static char readCPI2(byte[] code, int curBCI) { return (char) ByteUtils.beU2(code, curBCI + 1); } + /** + * Reads a 2-byte constant pool index for the current instruction, reading each byte with + * volatile semantics. Note that this does not read the 2 bytes atomically. + * + * @return the constant pool index + */ + public static char readCPI2Volatile(byte[] code, int curBCI) { + return (char) (((ByteUtils.volatileBeU1(code, curBCI + 1) & 0xff) << 8) | (ByteUtils.volatileBeU1(code, curBCI + 1 + 1) & 0xff)); + } + /** * Reads a constant pool index for the current instruction. * @@ -374,6 +384,18 @@ public static void patchAppendixCPI(byte[] code, int curBCI, int appendixCPI) { } } + public static void patchIndyExtraCPI(byte[] code, int curBCI, int extraCPI) { + int opcode = opcode(code, curBCI); + switch (opcode) { + case INVOKEDYNAMIC: + code[curBCI + 1] = (byte) ((extraCPI >> 8) & 0xFF); + code[curBCI + 2] = (byte) (extraCPI & 0xFF); + break; + default: + throw VMError.shouldNotReachHereAtRuntime(); + } + } + /** * No CPI patching at runtime. */ diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java index 72303747196e..2b203bdb153b 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java @@ -24,10 +24,17 @@ */ package com.oracle.svm.interpreter.metadata; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; import java.util.List; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.registry.SymbolsSupport; import com.oracle.svm.espresso.classfile.attributes.LineNumberTableAttribute; import com.oracle.svm.espresso.classfile.descriptors.ByteSequence; +import com.oracle.svm.espresso.classfile.descriptors.Name; +import com.oracle.svm.espresso.classfile.descriptors.ParserSymbols; import com.oracle.svm.espresso.classfile.descriptors.Signature; import com.oracle.svm.espresso.classfile.descriptors.SignatureSymbols; import com.oracle.svm.espresso.classfile.descriptors.Symbol; @@ -68,6 +75,35 @@ static InterpreterUnresolvedSignature toJVMCI(Symbol parserSignature, return InterpreterUnresolvedSignature.create(returnType, parameters); } + static InterpreterResolvedJavaMethod toJVMCI(Executable executable) { + InterpreterResolvedObjectType holder = (InterpreterResolvedObjectType) DynamicHub.fromClass(executable.getDeclaringClass()).getInterpreterType(); + Symbol name; + if (executable instanceof Constructor) { + name = ParserSymbols.ParserNames._init_; + } else { + /* + * Since we are looking for a method that already exists in the system, we expect the + * symbols to already exist for the name here as well as for the signature below. As a + * result we just perform a lookup instead of getOrCreate. + */ + name = SymbolsSupport.getNames().lookup(executable.getName()); + } + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (Class type : executable.getParameterTypes()) { + sb.append(type.descriptorString()); + } + sb.append(')'); + if (executable instanceof Method method) { + sb.append(method.getReturnType().descriptorString()); + } else { + assert executable instanceof Constructor; + sb.append('V'); + } + Symbol signature = SymbolsSupport.getSignatures().lookupValidSignature(sb.toString()); + return holder.lookupMethod(name, signature); + } + static Symbol toSymbol(InterpreterUnresolvedSignature jvmciSignature, SignatureSymbols signatures) { StringBuilder sb = new StringBuilder(); sb.append('('); diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaResolvedObjectType.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaResolvedObjectType.java index 13d83179bc36..b24c2ff04938 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaResolvedObjectType.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaResolvedObjectType.java @@ -32,6 +32,7 @@ import com.oracle.svm.core.hub.crema.CremaResolvedJavaType; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.espresso.classfile.attributes.BootstrapMethodsAttribute; import com.oracle.svm.espresso.classfile.ParserKlass; import com.oracle.svm.espresso.classfile.attributes.Attribute; import com.oracle.svm.espresso.classfile.attributes.AttributedElement; @@ -67,6 +68,10 @@ public Object getStaticStorage(boolean primitives, int layerNum) { return primitives ? primitiveStatics : referenceStatics; } + public BootstrapMethodsAttribute getBootstrapMethodsAttribute() { + return getAttribute(BootstrapMethodsAttribute.NAME, BootstrapMethodsAttribute.class); + } + @Override public CremaResolvedJavaFieldImpl[] getDeclaredFields() { return (CremaResolvedJavaFieldImpl[]) declaredFields; diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java index ca5b74bd9f15..e6485eeec741 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java @@ -183,7 +183,7 @@ protected InterpreterResolvedJavaMethod(InterpreterResolvedObjectType declaringC CodeAttribute codeAttribute = (CodeAttribute) m.getAttribute(CodeAttribute.NAME); if (codeAttribute != null) { this.maxLocals = codeAttribute.getMaxLocals(); - this.maxStackSize = codeAttribute.getMaxStack(); + this.maxStackSize = codeAttribute.getMaxStack() + 1; this.interpretedCode = codeAttribute.getOriginalCode(); this.lineNumberTable = CremaMethodAccess.toJVMCI(codeAttribute.getLineNumberTableAttribute()); } else { @@ -201,7 +201,6 @@ protected InterpreterResolvedJavaMethod(InterpreterResolvedObjectType declaringC this.enterStubOffset = EST_NO_ENTRY; this.methodId = UNKNOWN_METHOD_ID; this.inlinedBy = new InlinedBy(this, new HashSet<>()); - } @VisibleForSerialization diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CallSiteLink.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CallSiteLink.java new file mode 100644 index 000000000000..f5be86c1f281 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CallSiteLink.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025, 2025, 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.interpreter; + +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; + +/** + * An entry in a {@link ResolvedInvokeDynamicConstant} corresponding to a specific calls site (a + * method + bci pair). It may either be {@linkplain SuccessfulCallSiteLink successful} or + * {@linkplain FailedCallSiteLink failed}. + */ +public sealed interface CallSiteLink permits FailedCallSiteLink, SuccessfulCallSiteLink { + boolean matchesCallSite(InterpreterResolvedJavaMethod siteMethod, int siteBci); +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/FailedCallSiteLink.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/FailedCallSiteLink.java new file mode 100644 index 000000000000..f90b7c1e1dbb --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/FailedCallSiteLink.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025, 2025, 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.interpreter; + +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; + +/** + * An entry in a {@link ResolvedInvokeDynamicConstant} for call site where linking failed. + */ +public final class FailedCallSiteLink implements CallSiteLink { + private final InterpreterResolvedJavaMethod method; + private final int bci; + private final Throwable failure; + + FailedCallSiteLink(InterpreterResolvedJavaMethod method, int bci, Throwable failure) { + this.method = method; + this.bci = bci; + this.failure = failure; + } + + @Override + public boolean matchesCallSite(InterpreterResolvedJavaMethod siteMethod, int siteBci) { + return bci == siteBci && method.equals(siteMethod); + } + + public Throwable getFailure() { + return failure; + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java index f07b9c1e4b90..be276984c73d 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java @@ -1231,36 +1231,74 @@ private static int invoke(InterpreterFrame callerFrame, InterpreterResolvedJavaM boolean preferStayInInterpreter) { int invokeTop = top; - char cpi = BytecodeStream.readCPI2(code, curBCI); - InterpreterResolvedJavaMethod seedMethod = Interpreter.resolveMethod(method, opcode, cpi); - - boolean hasReceiver = !seedMethod.isStatic(); + InterpreterResolvedJavaMethod seedMethod; boolean isVirtual = opcode == INVOKEVIRTUAL || opcode == INVOKEINTERFACE; if (opcode == INVOKEDYNAMIC) { - int appendixCPI = BytecodeStream.readCPI4(code, curBCI) & 0xFFFF; - if (appendixCPI != 0) { - Object appendixEntry = method.getConstantPool().resolvedAt(appendixCPI, method.getDeclaringClass()); - Object appendix; + int fullCPI = BytecodeStream.readCPI4(code, curBCI); + if (GraalDirectives.injectBranchProbability(GraalDirectives.SLOWPATH_PROBABILITY, fullCPI == 0)) { + // This can happen for the debugger + throw noSuchMethodError(opcode, null); + } + int indyCPI = fullCPI >>> 16; + int extraCPI = fullCPI & 0xFFFF; + Object indyEntry = method.getConstantPool().resolvedAt(indyCPI, method.getDeclaringClass()); + Object appendix; + if (indyEntry instanceof ResolvedInvokeDynamicConstant invokeDynamicConstant) { + // runtime-loaded case + if (extraCPI == 0) { + // This call site is not linked yet + try { + extraCPI = invokeDynamicConstant.link((RuntimeInterpreterConstantPool) method.getConstantPool(), method.getDeclaringClass().getJavaClass(), method, curBCI); + assert extraCPI != 0; + } catch (Throwable e) { + throw SemanticJavaException.raise(e); + } + BytecodeStream.patchIndyExtraCPI(code, curBCI, extraCPI); + assert BytecodeStream.readCPI2Volatile(code, curBCI) == extraCPI; + } + CallSiteLink link = invokeDynamicConstant.getCallSiteLink(extraCPI); + while (!link.matchesCallSite(method, curBCI)) { + /* + * since the extra cpi read and write is not atomic, we might have read only 1 + * of the non-zero bytes. That is guaranteed to be <= the real extra CPI so it's + * still safe to use in `getCallSiteLink`. `matchesCallSite` ensures we have the + * full extraCPI. + */ + extraCPI = BytecodeStream.readCPI2Volatile(code, curBCI); + link = invokeDynamicConstant.getCallSiteLink(extraCPI); + } + if (link instanceof SuccessfulCallSiteLink successfulCallSiteLink) { + appendix = successfulCallSiteLink.getUnboxedAppendix(); + seedMethod = successfulCallSiteLink.getInvoker(); + } else { + throw SemanticJavaException.raise(((FailedCallSiteLink) link).getFailure()); + } + } else if (indyEntry instanceof InterpreterResolvedJavaMethod entryMethod) { + // AOT case + seedMethod = entryMethod; + Object appendixEntry = method.getConstantPool().resolvedAt(extraCPI, method.getDeclaringClass()); if (JavaConstant.NULL_POINTER.equals(appendixEntry)) { // The appendix is deliberately null. appendix = null; - } else { - if (appendixEntry instanceof ReferenceConstant referenceConstant) { - appendix = referenceConstant.getReferent(); - } else { - throw VMError.shouldNotReachHere("Unexpected INVOKEDYNAMIC appendix constant: " + appendixEntry); - } + } else if (appendixEntry instanceof ReferenceConstant referenceConstant) { + appendix = referenceConstant.getReferent(); if (appendix == null) { throw SemanticJavaException.raise(new IncompatibleClassChangeError("INVOKEDYNAMIC appendix was not included in the image heap")); } + } else { + throw VMError.shouldNotReachHere("Unexpected INVOKEDYNAMIC appendix constant: " + appendixEntry); } - EspressoFrame.putObject(callerFrame, top, appendix); - invokeTop = top + 1; } else { - throw VMError.shouldNotReachHere("Appendix-less INVOKEDYNAMIC"); + throw VMError.shouldNotReachHere("Unexpected INVOKEDYNAMIC constant: " + indyEntry); } + EspressoFrame.putObject(callerFrame, top, appendix); + invokeTop = top + 1; + } else { + char cpi = BytecodeStream.readCPI2(code, curBCI); + seedMethod = Interpreter.resolveMethod(method, opcode, cpi); } + boolean hasReceiver = !seedMethod.isStatic(); InterpreterUnresolvedSignature seedSignature = seedMethod.getSignature(); int resultAt = invokeTop - seedSignature.slotsForParameters(hasReceiver); diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ResolvedInvokeDynamicConstant.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ResolvedInvokeDynamicConstant.java new file mode 100644 index 000000000000..c4e10526b8bf --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ResolvedInvokeDynamicConstant.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2025, 2025, 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.interpreter; + +import static com.oracle.svm.interpreter.metadata.CremaMethodAccess.toJVMCI; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.lang.reflect.Executable; +import java.util.Arrays; + +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; +import com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives; +import com.oracle.svm.espresso.classfile.attributes.BootstrapMethodsAttribute; +import com.oracle.svm.espresso.classfile.descriptors.Name; +import com.oracle.svm.espresso.classfile.descriptors.Symbol; +import com.oracle.svm.espresso.classfile.descriptors.Type; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; + +/** + * The runtime constant pool entry for + * {@link com.oracle.svm.espresso.classfile.ConstantPool.Tag#INVOKEDYNAMIC} entries. + *

+ * Since {@code invokedynamic} are linked per call site, this contains a mapping from call sites to + * {@link CallSiteLink} objects that represent the linkage result. + */ +public final class ResolvedInvokeDynamicConstant { + private final BootstrapMethodsAttribute.Entry bootstrapMethod; + private final Symbol[] parsedInvokeSignature; + private final Symbol nameSymbol; + private volatile CallSiteLink[] callSiteLinks; + + ResolvedInvokeDynamicConstant(BootstrapMethodsAttribute.Entry bootstrapMethod, Symbol[] parsedInvokeSignature, Symbol name) { + this.bootstrapMethod = bootstrapMethod; + this.parsedInvokeSignature = parsedInvokeSignature; + this.nameSymbol = name; + } + + /** + * The call site linking operation must be executed once per {@code invokedynamic} instruction, + * rather than once per {@code invokedynamic_constant}. + *

+ * Furthermore, a previously failed call site linking from the constant pool must immediately + * fail again on subsequent linking operations, independently of the invokedynamic instruction + * involved. + *

+ * For example: + * + *

+     * method1:
+     *     0: invokedynamic #0 // calls MHN.linkCallSite successfully
+     *     4: invokedynamic #0 // Also calls MHM.linkCallSite, resulting in a *different* CallSite
+     *
+     * method2:
+     *     0: invokedynamic #1 // Fails during MHN.linkCallSite upcall
+     *     4: invokedynamic #1 // Immediately fails without calling MHN.linkCallSite
+     * 
+ * + * @return an index that can be stored in the available space in an {@code invokedynamic}'s + * constant pool index. It can be used to {@linkplain #getCallSiteLink(int) retrieve} + * the {@link CallSiteLink}. + */ + public int link(RuntimeInterpreterConstantPool pool, Class accessingKlass, InterpreterResolvedJavaMethod method, int bci) { + int extraCPI = getCallSiteLinkExtraCPI(method, bci); + if (extraCPI > 0) { + return extraCPI; + } + // The call site linking must not happen under the lock, it is racy + // However insertion should be done under a lock + // see JVMS sect. 5.4.3.6 & ConstantPoolCache::set_dynamic_call + CallSiteLink newLink = createCallSiteLink(pool, accessingKlass, method, bci); + synchronized (this) { + CallSiteLink[] existingLinks = callSiteLinks; + if (existingLinks == null) { + CallSiteLink[] newLinks = new CallSiteLink[1]; + newLinks[0] = newLink; + callSiteLinks = newLinks; + return 1; + } else { + int i = 0; + for (; i < existingLinks.length; i++) { + CallSiteLink link = existingLinks[i]; + if (link == null) { + existingLinks[i] = newLink; + return i + 1; + } + if (link.matchesCallSite(method, bci)) { + return i + 1; + } + } + // we didn't find an existing link nor an available slot + // the array growth is limited by the max number of possible bcis for a method + // (which is a power of 2). + CallSiteLink[] newLinks = Arrays.copyOf(existingLinks, existingLinks.length * 2); + newLinks[i] = newLink; + callSiteLinks = newLinks; + return i + 1; + } + } + } + + private int getCallSiteLinkExtraCPI(InterpreterResolvedJavaMethod method, int bci) { + CallSiteLink[] existingLinks = callSiteLinks; + if (existingLinks != null) { + for (int i = 0; i < existingLinks.length; i++) { + CallSiteLink link = existingLinks[i]; + if (link == null) { + break; + } + if (link.matchesCallSite(method, bci)) { + return i + 1; + } + } + } + return 0; + } + + public CallSiteLink getCallSiteLink(int extraCPI) { + return callSiteLinks[extraCPI - 1]; + } + + private CallSiteLink createCallSiteLink(RuntimeInterpreterConstantPool pool, Class accessingKlass, InterpreterResolvedJavaMethod method, int bci) { + // Per-callsite linking + try { + InterpreterResolvedObjectType accessingType = (InterpreterResolvedObjectType) DynamicHub.fromClass(accessingKlass).getInterpreterType(); + MethodHandle bootstrapmethodMethodHandle = pool.resolvedMethodHandleAt(bootstrapMethod.getBootstrapMethodRef(), accessingType); + Object[] args = pool.getStaticArguments(bootstrapMethod, accessingType); + + String name = nameSymbol.toString(); + MethodType methodType = RuntimeInterpreterConstantPool.signatureToMethodType(parsedInvokeSignature, accessingType); + /* + * the 4 objects resolved above are not actually call-site specific. We don't cache them + * to minimize footprint since most indy constant are only used by one call-site + */ + + Object[] appendix = new Object[1]; + Target_java_lang_invoke_MemberName memberName = Target_java_lang_invoke_MethodHandleNatives.linkCallSite( + accessingKlass, + bootstrapmethodMethodHandle, + name, methodType, + args, + appendix); + Object unboxedAppendix = appendix[0]; + Executable reflectInvoker = (Executable) memberName.reflectAccess; + return new SuccessfulCallSiteLink(method, bci, toJVMCI(reflectInvoker), unboxedAppendix); + } catch (LinkageError e) { + return new FailedCallSiteLink(method, bci, e); + } + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/RuntimeInterpreterConstantPool.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/RuntimeInterpreterConstantPool.java index 749446445268..fa99e995319c 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/RuntimeInterpreterConstantPool.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/RuntimeInterpreterConstantPool.java @@ -31,12 +31,14 @@ import com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives; import com.oracle.svm.core.util.VMError; import com.oracle.svm.espresso.classfile.ParserKlass; +import com.oracle.svm.espresso.classfile.attributes.BootstrapMethodsAttribute; import com.oracle.svm.espresso.classfile.descriptors.Name; import com.oracle.svm.espresso.classfile.descriptors.Signature; import com.oracle.svm.espresso.classfile.descriptors.SignatureSymbols; import com.oracle.svm.espresso.classfile.descriptors.Symbol; import com.oracle.svm.espresso.classfile.descriptors.Type; import com.oracle.svm.espresso.classfile.descriptors.TypeSymbols; +import com.oracle.svm.interpreter.metadata.CremaResolvedObjectType; import com.oracle.svm.interpreter.metadata.InterpreterConstantPool; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; @@ -71,10 +73,22 @@ protected Object resolve(int cpi, InterpreterResolvedObjectType accessingClass) case CLASS -> resolveClassConstant(cpi, accessingClass); case METHODTYPE -> resolveMethodType(cpi, accessingClass); case METHODHANDLE -> resolveMethodHandle(cpi, accessingClass); + case INVOKEDYNAMIC -> resolveInvokeDynamic(cpi, accessingClass); default -> throw VMError.unimplemented("Unimplemented CP resolution for " + tag); }; } + private Object resolveInvokeDynamic(int cpi, InterpreterResolvedObjectType accessingClass) { + BootstrapMethodsAttribute bms = ((CremaResolvedObjectType) accessingClass).getBootstrapMethodsAttribute(); + int bootstrapMethodAttrIndex = this.invokeDynamicBootstrapMethodAttrIndex(cpi); + BootstrapMethodsAttribute.Entry bsEntry = bms.at(bootstrapMethodAttrIndex); + + Symbol invokeSignature = this.invokeDynamicSignature(cpi); + Symbol[] parsedInvokeSignature = SymbolsSupport.getSignatures().parsed(invokeSignature); + + return new ResolvedInvokeDynamicConstant(bsEntry, parsedInvokeSignature, this.invokeDynamicName(cpi)); + } + private Object resolveMethodHandle(int cpi, InterpreterResolvedObjectType accessingClass) { Object mtype; InterpreterResolvedJavaType mklass; @@ -275,6 +289,24 @@ private InterpreterResolvedJavaMethod resolveInterfaceMethodRefConstant(int inte return interfaceMethod; } + public Object[] getStaticArguments(BootstrapMethodsAttribute.Entry entry, InterpreterResolvedObjectType accessingClass) { + Object[] args = new Object[entry.numBootstrapArguments()]; + for (int i = 0; i < entry.numBootstrapArguments(); i++) { + args[i] = switch (tagAt(entry.argAt(i))) { + case METHODHANDLE -> this.resolvedMethodHandleAt(entry.argAt(i), accessingClass); + case METHODTYPE -> this.resolvedMethodTypeAt(entry.argAt(i), accessingClass); + case CLASS -> this.resolveClassConstant(entry.argAt(i), accessingClass).getJavaClass(); + case STRING -> this.resolveStringAt(entry.argAt(i)); + case INTEGER -> this.intAt(entry.argAt(i)); + case LONG -> this.longAt(entry.argAt(i)); + case DOUBLE -> this.doubleAt(entry.argAt(i)); + case FLOAT -> this.floatAt(entry.argAt(i)); + default -> throw VMError.unimplemented("Unimplemented CP resolution for " + tagAt(entry.argAt(i))); + }; + } + return args; + } + public static MethodType signatureToMethodType(Symbol[] signature, InterpreterResolvedObjectType accessingClass) { Symbol rt = SignatureSymbols.returnType(signature); int pcount = SignatureSymbols.parameterCount(signature); diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/SuccessfulCallSiteLink.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/SuccessfulCallSiteLink.java new file mode 100644 index 000000000000..bd8fc9c6d729 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/SuccessfulCallSiteLink.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025, 2025, 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.interpreter; + +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; + +/** + * An entry in a {@link ResolvedInvokeDynamicConstant} containing an {@linkplain #getInvoker() + * invoker} and an {@linkplain #getUnboxedAppendix() appendix} that should be used to perform the + * call. + */ +public final class SuccessfulCallSiteLink implements CallSiteLink { + private final InterpreterResolvedJavaMethod method; + private final int bci; + private final InterpreterResolvedJavaMethod invoker; + private final Object unboxedAppendix; + + SuccessfulCallSiteLink(InterpreterResolvedJavaMethod method, int bci, InterpreterResolvedJavaMethod invoker, Object unboxedAppendix) { + this.method = method; + this.bci = bci; + this.invoker = invoker; + this.unboxedAppendix = unboxedAppendix; + } + + public InterpreterResolvedJavaMethod getInvoker() { + return invoker; + } + + public Object getUnboxedAppendix() { + return unboxedAppendix; + } + + @Override + public boolean matchesCallSite(InterpreterResolvedJavaMethod siteMethod, int siteBci) { + return bci == siteBci && method.equals(siteMethod); + } +} From 9e8e9de6a98e7abb3786e608cdf87bc4e811b825 Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Sat, 27 Sep 2025 18:21:52 +0200 Subject: [PATCH 03/14] Crema: ignore parallel class loading --- .../registry/AbstractRuntimeClassRegistry.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java index 3283fe7baf25..fa6b9c826425 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java @@ -175,20 +175,17 @@ private static boolean isParallelClassLoader(ClassLoader loader) { protected abstract boolean loaderIsBootOrPlatform(); public final Class defineClass(Symbol typeOrNull, byte[] b, int off, int len, ClassDefinitionInfo info) { - if (isParallelClassLoader()) { + // GR-62338: for parallel class loaders this synchronization should be skipped. + Object syncObject = getClassLoader(); + if (syncObject == null) { + syncObject = this; + } + synchronized (syncObject) { return defineClassInner(typeOrNull, b, off, len, info); - } else { - synchronized (getClassLoader()) { - return defineClassInner(typeOrNull, b, off, len, info); - } } } private Class defineClassInner(Symbol typeOrNull, byte[] b, int off, int len, ClassDefinitionInfo info) { - if (isParallelClassLoader() || getClassLoader() == null) { - // GR-62338 - throw VMError.unimplemented("Parallel class loading:" + getClassLoader()); - } byte[] data = b; if (off != 0 || b.length != len) { if (len < 0) { From eda94264842c090b3e1b6fa3c0b095f78af8217f Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Sat, 27 Sep 2025 19:05:05 +0200 Subject: [PATCH 04/14] Crema: resolve polymorphic signature methods --- .../svm/core/hub/crema/CremaSupport.java | 3 + .../InterpreterResolvedJavaMethod.java | 72 +++++++++++++++---- .../InterpreterResolvedObjectType.java | 46 +++++++++--- .../BuildTimeInterpreterUniverse.java | 3 + .../svm/interpreter/CremaSupportImpl.java | 8 +++ 5 files changed, 110 insertions(+), 22 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java index 9afe9d79d57e..50801a746cb4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java @@ -34,6 +34,7 @@ import com.oracle.svm.core.hub.registry.SymbolsSupport; import com.oracle.svm.espresso.classfile.ParserKlass; import com.oracle.svm.espresso.classfile.descriptors.ByteSequence; +import com.oracle.svm.espresso.classfile.descriptors.Signature; import com.oracle.svm.espresso.classfile.descriptors.Symbol; import com.oracle.svm.espresso.classfile.descriptors.Type; @@ -97,6 +98,8 @@ default Class findLoadedClass(JavaType unresolvedJavaType, ResolvedJavaType a Object getStaticStorage(Class cls, boolean primitives, int layerNum); + ResolvedJavaMethod findMethodHandleIntrinsic(ResolvedJavaMethod signaturePolymorphicMethod, Symbol signature); + static CremaSupport singleton() { return ImageSingletons.lookup(CremaSupport.class); } diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java index e6485eeec741..fc1f149d2c0b 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java @@ -24,9 +24,16 @@ */ package com.oracle.svm.interpreter.metadata; +import static com.oracle.svm.espresso.classfile.Constants.ACC_FINAL; +import static com.oracle.svm.espresso.classfile.Constants.ACC_NATIVE; +import static com.oracle.svm.espresso.classfile.Constants.ACC_SIGNATURE_POLYMORPHIC; +import static com.oracle.svm.espresso.classfile.Constants.ACC_STATIC; +import static com.oracle.svm.espresso.classfile.Constants.ACC_SYNTHETIC; +import static com.oracle.svm.espresso.classfile.Constants.ACC_VARARGS; import static com.oracle.svm.interpreter.metadata.Bytecodes.BREAKPOINT; import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.HashSet; import java.util.Set; @@ -41,12 +48,14 @@ import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.util.VMError; import com.oracle.svm.espresso.classfile.Constants; +import com.oracle.svm.espresso.classfile.JavaVersion; import com.oracle.svm.espresso.classfile.ParserMethod; import com.oracle.svm.espresso.classfile.attributes.CodeAttribute; import com.oracle.svm.espresso.classfile.descriptors.Name; import com.oracle.svm.espresso.classfile.descriptors.ParserSymbols; import com.oracle.svm.espresso.classfile.descriptors.Signature; import com.oracle.svm.espresso.classfile.descriptors.Symbol; +import com.oracle.svm.espresso.shared.meta.SignaturePolymorphicIntrinsic; import com.oracle.svm.espresso.shared.vtable.PartialMethod; import com.oracle.svm.interpreter.metadata.serialization.VisibleForSerialization; @@ -81,6 +90,8 @@ public class InterpreterResolvedJavaMethod implements ResolvedJavaMethod, CremaM private final int maxLocals; private final int maxStackSize; private final int modifiers; + private final boolean isSubstitutedNative; + private final boolean signaturePolymorphic; @Platforms(Platform.HOSTED_ONLY.class) // private ResolvedJavaMethod originalMethod; @@ -133,14 +144,17 @@ public InlinedBy(InterpreterResolvedJavaMethod holder, Set name, int maxLocals, int maxStackSize, int modifiers, InterpreterResolvedObjectType declaringClass, - InterpreterUnresolvedSignature signature, + InterpreterUnresolvedSignature signature, boolean isSubstitutedNative, byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { - this(name, maxLocals, maxStackSize, modifiers, declaringClass, signature, code, exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, - enterStubOffset, methodId); + this(name, maxLocals, maxStackSize, modifiers, isSubstitutedNative, declaringClass, signature, code, exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, + gotOffset, + enterStubOffset, methodId, null); this.originalMethod = originalMethod; this.needMethodBody = false; this.inlinedBy = new InterpreterResolvedJavaMethod.InlinedBy(this, new HashSet<>()); @@ -148,14 +162,15 @@ protected InterpreterResolvedJavaMethod(ResolvedJavaMethod originalMethod, Symbo private InterpreterResolvedJavaMethod(Symbol name, int maxLocals, int maxStackSize, - int modifiers, + int modifiers, boolean isSubstitutedNative, InterpreterResolvedObjectType declaringClass, InterpreterUnresolvedSignature signature, byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, - ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { + ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId, SignaturePolymorphicIntrinsic intrinsic) { this.name = name; this.maxLocals = maxLocals; this.maxStackSize = maxStackSize; this.modifiers = modifiers; + this.isSubstitutedNative = isSubstitutedNative; this.declaringClass = declaringClass; this.signature = signature; this.interpretedCode = code; @@ -171,6 +186,9 @@ private InterpreterResolvedJavaMethod(Symbol name, this.inlinedBy = new InlinedBy(this, new HashSet<>()); this.signatureSymbol = CremaMethodAccess.toSymbol(signature, SymbolsSupport.getSignatures()); + this.intrinsic = intrinsic; + this.signaturePolymorphic = ParserMethod.isDeclaredSignaturePolymorphic(declaringClass.getSymbolicType(), signatureSymbol, getOriginalModifiers(modifiers, isSubstitutedNative), + JavaVersion.HOST_VERSION); } protected InterpreterResolvedJavaMethod(InterpreterResolvedObjectType declaringClass, ParserMethod m, int vtableIndex) { @@ -180,6 +198,7 @@ protected InterpreterResolvedJavaMethod(InterpreterResolvedObjectType declaringC this.declaringClass = declaringClass; this.modifiers = m.getFlags() & Constants.JVM_RECOGNIZED_METHOD_MODIFIERS; + this.signaturePolymorphic = (m.getFlags() & ACC_SIGNATURE_POLYMORPHIC) != 0; CodeAttribute codeAttribute = (CodeAttribute) m.getAttribute(CodeAttribute.NAME); if (codeAttribute != null) { this.maxLocals = codeAttribute.getMaxLocals(); @@ -201,6 +220,8 @@ protected InterpreterResolvedJavaMethod(InterpreterResolvedObjectType declaringC this.enterStubOffset = EST_NO_ENTRY; this.methodId = UNKNOWN_METHOD_ID; this.inlinedBy = new InlinedBy(this, new HashSet<>()); + this.isSubstitutedNative = false; + this.intrinsic = null; } @VisibleForSerialization @@ -209,30 +230,55 @@ public static InterpreterResolvedJavaMethod create(String name, int maxLocals, i byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { Symbol nameSymbol = SymbolsSupport.getNames().getOrCreate(name); - return new InterpreterResolvedJavaMethod(nameSymbol, maxLocals, maxStackSize, modifiers, declaringClass, signature, code, - exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId); + return new InterpreterResolvedJavaMethod(nameSymbol, maxLocals, maxStackSize, modifiers, false, declaringClass, signature, code, + exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId, null); } // Only called during universe building @Platforms(Platform.HOSTED_ONLY.class) public static InterpreterResolvedJavaMethod create(ResolvedJavaMethod originalMethod, String name, int maxLocals, int maxStackSize, int modifiers, InterpreterResolvedObjectType declaringClass, - InterpreterUnresolvedSignature signature, + InterpreterUnresolvedSignature signature, boolean isSubstitutedNative, byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { Symbol nameSymbol = SymbolsSupport.getNames().getOrCreate(name); - return new InterpreterResolvedJavaMethod(originalMethod, nameSymbol, maxLocals, maxStackSize, modifiers, declaringClass, signature, code, + return new InterpreterResolvedJavaMethod(originalMethod, nameSymbol, maxLocals, maxStackSize, modifiers, declaringClass, signature, isSubstitutedNative, code, exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId); } @Override - public boolean isDeclaredSignaturePolymorphic() { - throw VMError.unimplemented("isDeclaredSignaturePolymorphic"); + public final boolean isDeclaredSignaturePolymorphic() { + // Note: might not be true for the instantiation of polymorphic signature intrinsics. + return signaturePolymorphic; } @Override - public InterpreterResolvedJavaMethod createSignaturePolymorphicIntrinsic(Symbol newSignature) { - throw VMError.unimplemented("createSignaturePolymorphicIntrinsic"); + public final InterpreterResolvedJavaMethod createSignaturePolymorphicIntrinsic(Symbol newSignature) { + SignaturePolymorphicIntrinsic iid = SignaturePolymorphicIntrinsic.getId(this); + assert iid != null; + assert intrinsic == null; + int newModifiers; + if (iid == SignaturePolymorphicIntrinsic.InvokeGeneric) { + newModifiers = getOriginalModifiers(modifiers, isSubstitutedNative) & ~ACC_VARARGS; + } else { + newModifiers = ACC_NATIVE | ACC_SYNTHETIC | ACC_FINAL; + if (iid.isStaticSignaturePolymorphic()) { + newModifiers |= ACC_STATIC; + } + } + assert Modifier.isNative(newModifiers); + InterpreterUnresolvedSignature jvmciSignature = CremaMethodAccess.toJVMCI(newSignature, SymbolsSupport.getTypes()); + return new InterpreterResolvedJavaMethod(name, jvmciSignature.getParameterCount(true), 0, newModifiers, isSubstitutedNative, declaringClass, jvmciSignature, + null, null, null, null, // not bytecode-interpretable + null, vtableIndex, gotOffset, enterStubOffset, methodId, iid); + } + + private static int getOriginalModifiers(int modifiers, boolean isSubstitutedNative) { + return modifiers | (isSubstitutedNative ? Modifier.NATIVE : 0); + } + + public final SignaturePolymorphicIntrinsic getSignaturePolymorphicIntrinsic() { + return intrinsic; } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedObjectType.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedObjectType.java index 4ced8238fa58..2a243181f986 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedObjectType.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedObjectType.java @@ -25,6 +25,7 @@ package com.oracle.svm.interpreter.metadata; import static com.oracle.svm.core.BuildPhaseProvider.AfterAnalysis; +import static com.oracle.svm.espresso.classfile.ParserKlass.isSignaturePolymorphicHolderType; import java.util.List; @@ -35,6 +36,7 @@ import com.oracle.svm.core.StaticFieldsSupport; import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.crema.CremaSupport; import com.oracle.svm.core.hub.registry.SymbolsSupport; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; import com.oracle.svm.core.util.VMError; @@ -53,7 +55,6 @@ import jdk.vm.ci.meta.ResolvedJavaType; public class InterpreterResolvedObjectType extends InterpreterResolvedJavaType { - private final InterpreterResolvedJavaType componentType; private final int modifiers; private final InterpreterResolvedObjectType superclass; @@ -388,17 +389,45 @@ public final InterpreterResolvedJavaField lookupField(Symbol name, Symbol< @Override public final InterpreterResolvedJavaMethod lookupMethod(Symbol name, Symbol signature) { InterpreterResolvedObjectType current = this; + InterpreterResolvedJavaMethod method = current.lookupDeclaredMethod(name, signature); + if (method != null) { + return method; + } + + current = current.getSuperclass(); while (current != null) { - for (InterpreterResolvedJavaMethod method : current.declaredMethods) { - if (name.equals(method.getSymbolicName()) && signature.equals(method.getSymbolicSignature())) { - return method; - } + method = current.lookupDeclaredMethod(name, signature); + if (method != null) { + return method; } current = current.getSuperclass(); } return null; } + private InterpreterResolvedJavaMethod lookupDeclaredMethod(Symbol name, Symbol signature) { + if (isSignaturePolymorphicHolderType(getSymbolicType())) { + InterpreterResolvedJavaMethod method = lookupSignaturePolymorphicMethod(name, signature); + if (method != null) { + return method; + } + } + for (InterpreterResolvedJavaMethod method : this.declaredMethods) { + if (name.equals(method.getSymbolicName()) && signature.equals(method.getSymbolicSignature())) { + return method; + } + } + return null; + } + + private InterpreterResolvedJavaMethod lookupSignaturePolymorphicMethod(Symbol methodName, Symbol signature) { + InterpreterResolvedJavaMethod m = lookupDeclaredSignaturePolymorphicMethod(methodName); + if (m != null) { + return (InterpreterResolvedJavaMethod) CremaSupport.singleton().findMethodHandleIntrinsic(m, signature); + } + return null; + } + @Override public final InterpreterResolvedJavaMethod lookupInstanceMethod(Symbol name, Symbol signature) { InterpreterResolvedObjectType current = this; @@ -416,10 +445,9 @@ public final InterpreterResolvedJavaMethod lookupInstanceMethod(Symbol nam @Override public final InterpreterResolvedJavaMethod lookupInterfaceMethod(Symbol name, Symbol signature) { assert isInterface(); - for (InterpreterResolvedJavaMethod method : declaredMethods) { - if (name.equals(method.getSymbolicName()) && signature.equals(method.getSymbolicSignature())) { - return method; - } + InterpreterResolvedJavaMethod result = lookupDeclaredMethod(name, signature); + if (result != null) { + return result; } throw VMError.unimplemented("lookupInterfaceMethod"); } diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java index 9a0476cd874d..764baac6a5af 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java @@ -201,9 +201,11 @@ public static InterpreterResolvedJavaMethod createResolveJavaMethod(ResolvedJava InterpreterUnresolvedSignature signature = universe.unresolvedSignature(originalMethod.getSignature()); byte[] interpretedCode = originalMethod.getCode() == null ? null : originalMethod.getCode().clone(); + boolean isSubstitutedNative = false; AnalysisMethod analysisMethod = (AnalysisMethod) originalMethod; if (analysisMethod.wrapped instanceof SubstitutionMethod substitutionMethod) { modifiers = substitutionMethod.getOriginal().getModifiers(); + isSubstitutedNative = Modifier.isNative(modifiers); if (substitutionMethod.hasBytecodes()) { /* * GR-53710: Keep bytecodes for substitutions, but only when there's no compiled @@ -224,6 +226,7 @@ public static InterpreterResolvedJavaMethod createResolveJavaMethod(ResolvedJava modifiers, declaringClass, signature, + isSubstitutedNative, interpretedCode, null, lineNumberTable, diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java index 365f2063aaa4..af71b4d13749 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java @@ -65,6 +65,7 @@ import com.oracle.svm.espresso.classfile.descriptors.Symbol; import com.oracle.svm.espresso.classfile.descriptors.Type; import com.oracle.svm.espresso.classfile.descriptors.TypeSymbols; +import com.oracle.svm.espresso.shared.meta.MethodHandleIntrinsics; import com.oracle.svm.espresso.shared.vtable.MethodTableException; import com.oracle.svm.espresso.shared.vtable.PartialMethod; import com.oracle.svm.espresso.shared.vtable.PartialType; @@ -86,6 +87,8 @@ import jdk.vm.ci.meta.ResolvedJavaType; public class CremaSupportImpl implements CremaSupport { + private final MethodHandleIntrinsics methodHandleIntrinsics = new MethodHandleIntrinsics<>(); + @Platforms(Platform.HOSTED_ONLY.class) @Override public ResolvedJavaType createInterpreterType(DynamicHub hub, ResolvedJavaType type) { @@ -733,4 +736,9 @@ public Object execute(ResolvedJavaMethod targetMethod, Object[] args) { public Object allocateInstance(ResolvedJavaType type) { return InterpreterToVM.createNewReference((InterpreterResolvedJavaType) type); } + + @Override + public ResolvedJavaMethod findMethodHandleIntrinsic(ResolvedJavaMethod signaturePolymorphicMethod, Symbol signature) { + return methodHandleIntrinsics.findIntrinsic((InterpreterResolvedJavaMethod) signaturePolymorphicMethod, signature, CremaRuntimeAccess.getInstance()); + } } From 5dece067d9a7b8a0508e8f7efc8813751f3e3d8e Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Sun, 28 Sep 2025 14:46:07 +0200 Subject: [PATCH 05/14] Setup the ability to interpret polymorphic signature intrinsics * Polymorphic signature method instantiations have an "intrinsic" field set denoting which kind of intrinsic type it is. * The interpreter can dispatch to `IntrinsicRoot.execute` for intrinsics. * Stack walking knows about this interpeter method * The special interpreter methods should be marked as `@NeverInline` and don't need to be added as root. * Add support in the interpreter for `invokeBasic` --- .../core/interpreter/InterpreterSupport.java | 12 +- .../MethodHandleInterpreterUtils.java | 40 +++++ .../svm/core/stack/JavaStackFrameVisitor.java | 10 +- .../InterpreterResolvedJavaMethod.java | 7 + .../oracle/svm/interpreter/Interpreter.java | 154 +++++++++++++++++- .../svm/interpreter/InterpreterFeature.java | 12 +- .../interpreter/InterpreterSupportImpl.java | 63 +++++-- .../svm/interpreter/InterpreterToVM.java | 3 +- .../ResolvedInvokeDynamicConstant.java | 7 +- 9 files changed, 264 insertions(+), 44 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/MethodHandleInterpreterUtils.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/interpreter/InterpreterSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/interpreter/InterpreterSupport.java index 6cb7ff976d10..91245348e278 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/interpreter/InterpreterSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/interpreter/InterpreterSupport.java @@ -64,16 +64,14 @@ public static InterpreterSupport singleton() { return ImageSingletons.lookup(InterpreterSupport.class); } - /* - * Check if a given argument matches the inner class Interpreter.Root (holder of the interpreter - * dispatch loop). + /** + * Check if a given frame should be processed by {@link #getInterpretedMethodFrameInfo}. */ - public abstract boolean isInterpreterRoot(Class clazz); + public abstract boolean isInterpreterRoot(FrameInfoQueryResult frameInfo); /** - * Transforms an interpreter (root) frame into a frame of the interpreted method. The passed - * frame must be an interpreter root e.g. {@code isInterpreterRoot(frameInfo.getSourceClass())} - * otherwise a fatal exception is thrown. + * Transforms an interpreter (root) frame into a frame of the interpreted method. An error is + * thrown if the passed frame is not an {@link #isInterpreterRoot interpreter root}. * * @param frameInfo interpreter root frame * @param sp stack pointer of the interpreter frame diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/MethodHandleInterpreterUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/MethodHandleInterpreterUtils.java new file mode 100644 index 000000000000..718b2348811a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/MethodHandleInterpreterUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.methodhandles; + +import java.lang.invoke.MethodHandle; + +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; + +public final class MethodHandleInterpreterUtils { + private MethodHandleInterpreterUtils() { + } + + public static Target_java_lang_invoke_MemberName extractVMEntry(MethodHandle handle) { + Target_java_lang_invoke_LambdaForm lform = SubstrateUtil.cast(handle, Target_java_lang_invoke_MethodHandle.class).internalForm(); + return lform.vmentry; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackFrameVisitor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackFrameVisitor.java index 15fec0698dc9..c7e57f858d66 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackFrameVisitor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/JavaStackFrameVisitor.java @@ -59,15 +59,9 @@ protected boolean visitDeoptimizedFrame(Pointer originalSP, CodePointer deoptStu return true; } - private static FrameSourceInfo interpreterToInterpretedMethodFrame(FrameInfoQueryResult frameInfo, Pointer sp) { - InterpreterSupport interpreter = InterpreterSupport.singleton(); - VMError.guarantee(interpreter.isInterpreterRoot(frameInfo.getSourceClass())); - return interpreter.getInterpretedMethodFrameInfo(frameInfo, sp); - } - protected final boolean dispatchPossiblyInterpretedFrame(FrameInfoQueryResult frameInfo, Pointer sp) { - if (InterpreterSupport.isEnabled() && InterpreterSupport.singleton().isInterpreterRoot(frameInfo.getSourceClass())) { - return visitFrame(interpreterToInterpretedMethodFrame(frameInfo, sp), sp); + if (InterpreterSupport.isEnabled() && InterpreterSupport.singleton().isInterpreterRoot(frameInfo)) { + return visitFrame(InterpreterSupport.singleton().getInterpretedMethodFrameInfo(frameInfo, sp), sp); } else { return visitFrame(frameInfo, sp); } diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java index fc1f149d2c0b..6b4145b72f35 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java @@ -31,8 +31,10 @@ import static com.oracle.svm.espresso.classfile.Constants.ACC_SYNTHETIC; import static com.oracle.svm.espresso.classfile.Constants.ACC_VARARGS; import static com.oracle.svm.interpreter.metadata.Bytecodes.BREAKPOINT; +import static com.oracle.svm.interpreter.metadata.CremaMethodAccess.toJVMCI; import java.lang.annotation.Annotation; +import java.lang.reflect.Executable; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.HashSet; @@ -45,6 +47,7 @@ import com.oracle.svm.core.FunctionPointerHolder; import com.oracle.svm.core.hub.RuntimeClassLoading; import com.oracle.svm.core.hub.registry.SymbolsSupport; +import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.util.VMError; import com.oracle.svm.espresso.classfile.Constants; @@ -739,4 +742,8 @@ public final void reprofile() { } // endregion Unimplemented methods + + public static InterpreterResolvedJavaMethod fromMemberName(Target_java_lang_invoke_MemberName memberName) { + return toJVMCI((Executable) memberName.reflectAccess); + } } diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java index be276984c73d..74494e8e8e11 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java @@ -266,10 +266,15 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; +import java.util.Objects; +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.jdk.InternalVMMethod; +import com.oracle.svm.core.methodhandles.MethodHandleInterpreterUtils; import com.oracle.svm.core.util.VMError; import com.oracle.svm.espresso.classfile.ConstantPool; +import com.oracle.svm.espresso.shared.meta.SignaturePolymorphicIntrinsic; import com.oracle.svm.interpreter.debug.DebuggerEvents; import com.oracle.svm.interpreter.debug.EventKind; import com.oracle.svm.interpreter.debug.SteppingControl; @@ -364,7 +369,7 @@ public static Object execute(InterpreterResolvedJavaMethod method, InterpreterFr public static Object execute(InterpreterResolvedJavaMethod method, Object[] args, boolean forceStayInInterpreter) { InterpreterFrame frame = EspressoFrame.allocate(method.getMaxLocals(), method.getMaxStackSize(), args); - InterpreterUtil.guarantee(!method.isNative(), "trying to interpret native method %s", method); + InterpreterUtil.guarantee(!method.isNative() || method.getSignaturePolymorphicIntrinsic() != null, "trying to interpret native method %s", method); initializeFrame(frame, method); return execute0(method, frame, forceStayInInterpreter); @@ -379,8 +384,13 @@ private static Object execute0(InterpreterResolvedJavaMethod method, Interpreter assert lockTarget != null; InterpreterToVM.monitorEnter(frame, nullCheck(lockTarget)); } - int startTop = startingStackOffset(method.getMaxLocals()); - return Root.executeBodyFromBCI(frame, method, 0, startTop, stayInInterpreter); + SignaturePolymorphicIntrinsic intrinsic = method.getSignaturePolymorphicIntrinsic(); + if (intrinsic != null) { + return IntrinsicRoot.execute(frame, method, intrinsic, stayInInterpreter); + } else { + int startTop = startingStackOffset(method.getMaxLocals()); + return Root.executeBodyFromBCI(frame, method, 0, startTop, stayInInterpreter); + } } finally { InterpreterToVM.releaseInterpreterFrameLocks(frame); } @@ -467,8 +477,144 @@ private static void traceInterpreterException(InterpreterResolvedJavaMethod meth .string("/top=").unsigned(top).newline(); } - public static final class Root { + private static void traceIntrinsicEnter(InterpreterResolvedJavaMethod method, int indent, SignaturePolymorphicIntrinsic intrinsic) { + /* arguments to Log methods might have side-effects */ + if (!InterpreterOptions.InterpreterTraceSupport.getValue()) { + return; + } + + setLogIndent(indent + 2); + traceInterpreter(" ".repeat(indent)) // + .string("[interp] Intrinsic Entered ") // + .string(method.getDeclaringClass().getName()) // + .string("::") // + .string(method.getName()) // + .string(method.getSignature().toMethodDescriptor()) // + .string(" with iid=").string(intrinsic.name()) // + .newline(); + } + + private static void traceInvokeBasic(InterpreterResolvedJavaMethod target, int indent) { + /* arguments to Log methods might have side-effects */ + if (!InterpreterOptions.InterpreterTraceSupport.getValue()) { + return; + } + + traceInterpreter(" ".repeat(indent)) // + .string("invokeBasic target=") // + .string(target.getDeclaringClass().getName()) // + .string("::") // + .string(target.getName()) // + .string(target.getSignature().toMethodDescriptor()) // + .newline(); + } + private static void traceLinkTo(InterpreterResolvedJavaMethod target, SignaturePolymorphicIntrinsic intrinsic, int indent) { + /* arguments to Log methods might have side-effects */ + if (!InterpreterOptions.InterpreterTraceSupport.getValue()) { + return; + } + + traceInterpreter(" ".repeat(indent)) // + .string(intrinsic.name()) + .string(" target=") // + .string(target.getDeclaringClass().getName()) // + .string("::") // + .string(target.getName()) // + .string(target.getSignature().toMethodDescriptor()) // + .newline(); + } + + public static final class IntrinsicRoot { + @NeverInline("needed far stack walking") + public static Object execute(InterpreterFrame frame, InterpreterResolvedJavaMethod method, SignaturePolymorphicIntrinsic intrinsic, boolean forceStayInInterpreter) { + int indent = getLogIndent(); + traceIntrinsicEnter(method, indent, intrinsic); + return switch (intrinsic) { + case InvokeBasic -> { + MethodHandle mh = (MethodHandle) EspressoFrame.getThis(frame); + Target_java_lang_invoke_MemberName vmentry = MethodHandleInterpreterUtils.extractVMEntry(mh); + InterpreterResolvedJavaMethod target = InterpreterResolvedJavaMethod.fromMemberName(vmentry); + Object[] calleeArgs = frame.getArguments(); + // This should integrate with the debugger GR-70801 + boolean preferStayInInterpreter = forceStayInInterpreter; + traceInvokeBasic(target, indent); + try { + yield InterpreterToVM.dispatchInvocation(target, calleeArgs, false, forceStayInInterpreter, preferStayInInterpreter, false); + } catch (SemanticJavaException e) { + throw uncheckedThrow(e.getCause()); + } + } + case LinkToStatic, LinkToSpecial, LinkToVirtual, LinkToInterface -> { + InterpreterResolvedJavaMethod resolutionSeed = getLinkToTarget(frame); + InterpreterUnresolvedSignature signature = resolutionSeed.getSignature(); + Object[] basicArgs = unbasic(frame, signature, false); + // This should integrate with the debugger GR-70801 + boolean preferStayInInterpreter = forceStayInInterpreter; + traceLinkTo(resolutionSeed, intrinsic, indent); + try { + boolean isInvokeInterface = intrinsic == SignaturePolymorphicIntrinsic.LinkToInterface; + boolean isVirtual = isInvokeInterface || intrinsic == SignaturePolymorphicIntrinsic.LinkToVirtual; + Object result = InterpreterToVM.dispatchInvocation(resolutionSeed, basicArgs, isVirtual, forceStayInInterpreter, preferStayInInterpreter, isInvokeInterface); + yield rebasic(result, signature.getReturnKind()); + } catch (SemanticJavaException e) { + throw uncheckedThrow(e.getCause()); + } + } + default -> throw VMError.shouldNotReachHere(Objects.toString(intrinsic)); + }; + } + } + + private static InterpreterResolvedJavaMethod getLinkToTarget(InterpreterFrame frame) { + Object[] arguments = frame.getArguments(); + Target_java_lang_invoke_MemberName memberName = (Target_java_lang_invoke_MemberName) arguments[arguments.length - 1]; + return InterpreterResolvedJavaMethod.fromMemberName(memberName); + } + + private static Object[] unbasic(InterpreterFrame frame, InterpreterUnresolvedSignature targetSig, boolean inclReceiver) { + Object[] arguments = frame.getArguments(); + int parameterCount = targetSig.getParameterCount(inclReceiver); + Object[] res = new Object[parameterCount]; + int start = 0; + if (inclReceiver) { + res[start++] = arguments[0]; + } + for (int i = start; i < parameterCount; i++) { + JavaKind kind = targetSig.getParameterKind(i - start); + res[i] = unbasic(arguments[i], kind); + } + return res; + } + + // Transforms ints to sub-words + public static Object unbasic(Object arg, JavaKind kind) { + return switch (kind) { + case Boolean -> (int) arg != 0; + case Byte -> (byte) (int) arg; + case Char -> (char) (int) arg; + case Short -> (short) (int) arg; + default -> arg; + }; + } + + private static Object rebasic(Object value, JavaKind returnType) { + // @formatter:off + return switch (returnType) { + case Boolean -> stackIntToBoolean((int) value); + case Byte -> (byte) value; + case Short -> (short) value; + case Char -> (char) value; + case Int, Long, Float, Double, Object + -> value; + case Void -> null; // void + default -> throw VMError.shouldNotReachHereAtRuntime(); + }; + // @formatter:on + } + + public static final class Root { + @NeverInline("needed far stack walking") private static Object executeBodyFromBCI(InterpreterFrame frame, InterpreterResolvedJavaMethod method, int startBCI, int startTop, boolean forceStayInInterpreter) { int curBCI = startBCI; diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFeature.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFeature.java index 3cfd0b029509..8d9707567f35 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFeature.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFeature.java @@ -200,20 +200,26 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { BuildTimeInterpreterUniverse.freshSingletonInstance(); AnalysisMethod interpreterRoot = accessImpl.getMetaAccess().lookupJavaType(Interpreter.Root.class).getDeclaredMethods(false)[0]; - accessImpl.registerAsRoot(interpreterRoot, true, "interpreter main loop"); LocalVariableTable interpreterVariableTable = interpreterRoot.getLocalVariableTable(); - int interpretedMethodSlot = findLocalSlotByName("method", interpreterVariableTable.getLocalsAt(0)); // parameter + int interpreterMethodSlot = findLocalSlotByName("method", interpreterVariableTable.getLocalsAt(0)); // parameter int interpreterFrameSlot = findLocalSlotByName("frame", interpreterVariableTable.getLocalsAt(0)); // parameter // Local variable, search all locals. int bciSlot = findLocalSlotByName("curBCI", interpreterVariableTable.getLocals()); - ImageSingletons.add(InterpreterSupport.class, new InterpreterSupportImpl(bciSlot, interpretedMethodSlot, interpreterFrameSlot)); + AnalysisMethod intrinsicRoot = accessImpl.getMetaAccess().lookupJavaType(Interpreter.IntrinsicRoot.class).getDeclaredMethods(false)[0]; + + LocalVariableTable intrinsicVariableTable = intrinsicRoot.getLocalVariableTable(); + int intrinsicMethodSlot = findLocalSlotByName("method", intrinsicVariableTable.getLocalsAt(0)); // parameter + int intrinsicFrameSlot = findLocalSlotByName("frame", intrinsicVariableTable.getLocalsAt(0)); // parameter + + ImageSingletons.add(InterpreterSupport.class, new InterpreterSupportImpl(bciSlot, interpreterMethodSlot, interpreterFrameSlot, intrinsicMethodSlot, intrinsicFrameSlot)); ImageSingletons.add(InterpreterDirectivesSupport.class, new InterpreterDirectivesSupportImpl()); ImageSingletons.add(InterpreterMethodPointerHolder.class, new InterpreterMethodPointerHolder()); // Locals must be available at runtime to retrieve BCI, interpreted method and interpreter // frame. SubstrateCompilationDirectives.singleton().registerFrameInformationRequired(interpreterRoot); + SubstrateCompilationDirectives.singleton().registerFrameInformationRequired(intrinsicRoot); Method leaveMethod = ReflectionUtil.lookupMethod(InterpreterStubSection.class, "leaveInterpreterStub", CFunctionPointer.class, Pointer.class, long.class, long.class); leaveStub = accessImpl.getMetaAccess().lookupJavaMethod(leaveMethod); diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterSupportImpl.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterSupportImpl.java index b66277416bf0..044c509411b2 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterSupportImpl.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterSupportImpl.java @@ -25,6 +25,8 @@ package com.oracle.svm.interpreter; +import static com.oracle.svm.core.code.FrameSourceInfo.LINENUMBER_NATIVE; + import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -47,16 +49,28 @@ public final class InterpreterSupportImpl extends InterpreterSupport { private final int bciSlot; private final int interpretedMethodSlot; private final int interpretedFrameSlot; + private final int intrinsicMethodSlot; + private final int intrinsicFrameSlot; - InterpreterSupportImpl(int bciSlot, int interpretedMethodSlot, int interpretedFrameSlot) { + InterpreterSupportImpl(int bciSlot, int interpretedMethodSlot, int interpretedFrameSlot, int intrinsicMethodSlot, int intrinsicFrameSlot) { this.bciSlot = bciSlot; this.interpretedMethodSlot = interpretedMethodSlot; this.interpretedFrameSlot = interpretedFrameSlot; + this.intrinsicMethodSlot = intrinsicMethodSlot; + this.intrinsicFrameSlot = intrinsicFrameSlot; } @Override - public boolean isInterpreterRoot(Class clazz) { - return Interpreter.Root.class.equals(clazz); + public boolean isInterpreterRoot(FrameInfoQueryResult frameInfo) { + return isInterpreterBytecodeRoot(frameInfo) || isInterpreterIntrinsicRoot(frameInfo); + } + + private static boolean isInterpreterBytecodeRoot(FrameInfoQueryResult frameInfo) { + return Interpreter.Root.class.equals(frameInfo.getSourceClass()); + } + + private static boolean isInterpreterIntrinsicRoot(FrameInfoQueryResult frameInfo) { + return Interpreter.IntrinsicRoot.class.equals(frameInfo.getSourceClass()); } private static int readInt(Pointer addr, SignedWord offset) { @@ -75,6 +89,11 @@ private InterpreterResolvedJavaMethod readInterpretedMethod(FrameInfoQueryResult return readObject(sp, Word.signed(valueInfo.getData()), valueInfo.isCompressedReference()); } + private InterpreterResolvedJavaMethod readIntrinsicMethod(FrameInfoQueryResult frameInfo, Pointer sp) { + FrameInfoQueryResult.ValueInfo valueInfo = frameInfo.getValueInfos()[intrinsicMethodSlot]; + return readObject(sp, Word.signed(valueInfo.getData()), valueInfo.isCompressedReference()); + } + private int readBCI(FrameInfoQueryResult frameInfo, Pointer sp) { FrameInfoQueryResult.ValueInfo valueInfo = frameInfo.getValueInfos()[bciSlot]; return readInt(sp, Word.signed(valueInfo.getData())); @@ -85,23 +104,35 @@ private InterpreterFrame readInterpreterFrame(FrameInfoQueryResult frameInfo, Po return readObject(sp, Word.signed(valueInfo.getData()), valueInfo.isCompressedReference()); } + private InterpreterFrame readIntrinsicFrame(FrameInfoQueryResult frameInfo, Pointer sp) { + FrameInfoQueryResult.ValueInfo valueInfo = frameInfo.getValueInfos()[intrinsicFrameSlot]; + return readObject(sp, Word.signed(valueInfo.getData()), valueInfo.isCompressedReference()); + } + @Override public FrameSourceInfo getInterpretedMethodFrameInfo(FrameInfoQueryResult frameInfo, Pointer sp) { - if (!isInterpreterRoot(frameInfo.getSourceClass())) { - throw VMError.shouldNotReachHereAtRuntime(); + if (isInterpreterBytecodeRoot(frameInfo)) { + InterpreterResolvedJavaMethod interpretedMethod = readInterpretedMethod(frameInfo, sp); + int bci = readBCI(frameInfo, sp); + InterpreterFrame interpreterFrame = readInterpreterFrame(frameInfo, sp); + Class interpretedClass = interpretedMethod.getDeclaringClass().getJavaClass(); + String sourceMethodName = interpretedMethod.getName(); + LineNumberTable lineNumberTable = interpretedMethod.getLineNumberTable(); + + int sourceLineNumber = -1; // unknown + if (lineNumberTable != null) { + sourceLineNumber = lineNumberTable.getLineNumber(bci); + } + return new InterpreterFrameSourceInfo(interpretedClass, sourceMethodName, sourceLineNumber, bci, interpretedMethod, interpreterFrame); } - InterpreterResolvedJavaMethod interpretedMethod = readInterpretedMethod(frameInfo, sp); - int bci = readBCI(frameInfo, sp); - InterpreterFrame interpreterFrame = readInterpreterFrame(frameInfo, sp); - Class interpretedClass = interpretedMethod.getDeclaringClass().getJavaClass(); - String sourceMethodName = interpretedMethod.getName(); - LineNumberTable lineNumberTable = interpretedMethod.getLineNumberTable(); - - int sourceLineNumber = -1; // unknown - if (lineNumberTable != null) { - sourceLineNumber = lineNumberTable.getLineNumber(bci); + if (isInterpreterIntrinsicRoot(frameInfo)) { + InterpreterResolvedJavaMethod intrinsicMethod = readIntrinsicMethod(frameInfo, sp); + InterpreterFrame interpreterFrame = readIntrinsicFrame(frameInfo, sp); + Class intrinsicClass = intrinsicMethod.getDeclaringClass().getJavaClass(); + String sourceMethodName = intrinsicMethod.getName(); + return new InterpreterFrameSourceInfo(intrinsicClass, sourceMethodName, LINENUMBER_NATIVE, -1, intrinsicMethod, interpreterFrame); } - return new InterpreterFrameSourceInfo(interpretedClass, sourceMethodName, sourceLineNumber, bci, interpretedMethod, interpreterFrame); + throw VMError.shouldNotReachHereAtRuntime(); } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java index 08faf1d24230..ea2ae9197c54 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java @@ -865,9 +865,8 @@ public static Object dispatchInvocation(InterpreterResolvedJavaMethod seedMethod } } - if (!goThroughPLT && targetMethod.isNative()) { + if (!goThroughPLT && (targetMethod.isNative() && targetMethod.getSignaturePolymorphicIntrinsic() == null)) { /* no way to execute target in interpreter, fall back to compiled code */ - /* example: MethodHandle.invokeBasic */ VMError.guarantee(targetMethod.hasNativeEntryPoint()); calleeFtnPtr = targetMethod.getNativeEntryPoint(); VMError.guarantee(calleeFtnPtr.isNonNull()); diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ResolvedInvokeDynamicConstant.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ResolvedInvokeDynamicConstant.java index c4e10526b8bf..d08f9f098063 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ResolvedInvokeDynamicConstant.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/ResolvedInvokeDynamicConstant.java @@ -24,11 +24,10 @@ */ package com.oracle.svm.interpreter; -import static com.oracle.svm.interpreter.metadata.CremaMethodAccess.toJVMCI; +import static com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod.fromMemberName; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; -import java.lang.reflect.Executable; import java.util.Arrays; import com.oracle.svm.core.hub.DynamicHub; @@ -165,8 +164,8 @@ private CallSiteLink createCallSiteLink(RuntimeInterpreterConstantPool pool, Cla args, appendix); Object unboxedAppendix = appendix[0]; - Executable reflectInvoker = (Executable) memberName.reflectAccess; - return new SuccessfulCallSiteLink(method, bci, toJVMCI(reflectInvoker), unboxedAppendix); + InterpreterResolvedJavaMethod invoker = fromMemberName(memberName); + return new SuccessfulCallSiteLink(method, bci, invoker, unboxedAppendix); } catch (LinkageError e) { return new FailedCallSiteLink(method, bci, e); } From ca98011d4c654f4b80019e603723e9ec07012fbc Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Sun, 28 Sep 2025 14:58:43 +0200 Subject: [PATCH 06/14] Add a quiet mode for `InterpreterToVM.dispatchInvocation` --- .../oracle/svm/interpreter/Interpreter.java | 6 +++--- .../svm/interpreter/InterpreterToVM.java | 20 ++++++++++--------- .../svm/jdwp/resident/impl/ResidentJDWP.java | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java index 74494e8e8e11..b32425e45d94 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java @@ -540,7 +540,7 @@ public static Object execute(InterpreterFrame frame, InterpreterResolvedJavaMeth boolean preferStayInInterpreter = forceStayInInterpreter; traceInvokeBasic(target, indent); try { - yield InterpreterToVM.dispatchInvocation(target, calleeArgs, false, forceStayInInterpreter, preferStayInInterpreter, false); + yield InterpreterToVM.dispatchInvocation(target, calleeArgs, false, forceStayInInterpreter, preferStayInInterpreter, false, false); } catch (SemanticJavaException e) { throw uncheckedThrow(e.getCause()); } @@ -555,7 +555,7 @@ public static Object execute(InterpreterFrame frame, InterpreterResolvedJavaMeth try { boolean isInvokeInterface = intrinsic == SignaturePolymorphicIntrinsic.LinkToInterface; boolean isVirtual = isInvokeInterface || intrinsic == SignaturePolymorphicIntrinsic.LinkToVirtual; - Object result = InterpreterToVM.dispatchInvocation(resolutionSeed, basicArgs, isVirtual, forceStayInInterpreter, preferStayInInterpreter, isInvokeInterface); + Object result = InterpreterToVM.dispatchInvocation(resolutionSeed, basicArgs, isVirtual, forceStayInInterpreter, preferStayInInterpreter, isInvokeInterface, false); yield rebasic(result, signature.getReturnKind()); } catch (SemanticJavaException e) { throw uncheckedThrow(e.getCause()); @@ -1455,7 +1455,7 @@ private static int invoke(InterpreterFrame callerFrame, InterpreterResolvedJavaM if (!seedMethod.isStatic()) { nullCheck(calleeArgs[0]); } - Object retObj = InterpreterToVM.dispatchInvocation(seedMethod, calleeArgs, isVirtual, forceStayInInterpreter, preferStayInInterpreter, opcode == INVOKEINTERFACE); + Object retObj = InterpreterToVM.dispatchInvocation(seedMethod, calleeArgs, isVirtual, forceStayInInterpreter, preferStayInInterpreter, opcode == INVOKEINTERFACE, false); retStackEffect += EspressoFrame.putKind(callerFrame, resultAt, retObj, seedSignature.getReturnKind()); diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java index ea2ae9197c54..0f4ade6b04b4 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterToVM.java @@ -752,7 +752,7 @@ private static int determineITableStartingIndex(DynamicHub thisHub, int interfac } public static Object dispatchInvocation(InterpreterResolvedJavaMethod seedMethod, Object[] calleeArgs, boolean isVirtual0, boolean forceStayInInterpreter, boolean preferStayInInterpreter, - boolean isInvokeInterface) + boolean isInvokeInterface, boolean quiet) throws SemanticJavaException { boolean goThroughPLT; boolean isVirtual = isVirtual0; @@ -779,7 +779,9 @@ public static Object dispatchInvocation(InterpreterResolvedJavaMethod seedMethod if (goThroughPLT) { if (seedMethod.hasNativeEntryPoint()) { calleeFtnPtr = seedMethod.getNativeEntryPoint(); - traceInterpreter("got native entry point: ").hex(calleeFtnPtr).newline(); + if (!quiet) { + traceInterpreter("got native entry point: ").hex(calleeFtnPtr).newline(); + } } else if (seedMethod.getVTableIndex() == VTBL_NO_ENTRY) { /* * does not always hold. Counter example: j.io.BufferedWriter::min, because it gets @@ -790,7 +792,7 @@ public static Object dispatchInvocation(InterpreterResolvedJavaMethod seedMethod goThroughPLT = false; /* arguments to Log methods might have side-effects */ - if (InterpreterTraceSupport.getValue()) { + if (InterpreterTraceSupport.getValue() && !quiet) { traceInterpreter("fall back to interp for compile entry ").string(seedMethod.toString()).string(" because it has not been compiled.").newline(); } } else if (seedMethod.getVTableIndex() == VTBL_ONE_IMPL) { @@ -798,7 +800,7 @@ public static Object dispatchInvocation(InterpreterResolvedJavaMethod seedMethod } else if (!isVirtual && seedMethod.hasVTableIndex()) { goThroughPLT = false; /* arguments to Log methods might have side-effects */ - if (InterpreterTraceSupport.getValue()) { + if (InterpreterTraceSupport.getValue() && !quiet) { traceInterpreter("invokespecial: ").string(seedMethod.toString()).newline(); } } else if (isVirtual && !seedMethod.hasVTableIndex()) { @@ -810,7 +812,7 @@ public static Object dispatchInvocation(InterpreterResolvedJavaMethod seedMethod if (isVirtual && (seedMethod.isFinalFlagSet() || calleeArgs[0].getClass().isArray() || seedDeclaringClass.isLeaf() || seedMethod.isPrivate())) { isVirtual = false; /* arguments to Log methods might have side-effects */ - if (InterpreterTraceSupport.getValue()) { + if (InterpreterTraceSupport.getValue() && !quiet) { traceInterpreter("reverting virtual call to invokespecial: ").string(seedMethod.toString()).newline(); } } @@ -835,7 +837,7 @@ public static Object dispatchInvocation(InterpreterResolvedJavaMethod seedMethod goThroughPLT = false; /* arguments to Log methods might have side-effects */ - if (InterpreterTraceSupport.getValue()) { + if (InterpreterTraceSupport.getValue() && !quiet) { traceInterpreter("fall back to interp (vtable entry) for compile entry ").string(seedMethod.toString()).string(" because it has not been compiled.").newline(); } } @@ -846,7 +848,7 @@ public static Object dispatchInvocation(InterpreterResolvedJavaMethod seedMethod } else if (seedMethod.getVTableIndex() == VTBL_ONE_IMPL) { targetMethod = seedMethod.getOneImplementation(); /* arguments to Log methods might have side-effects */ - if (InterpreterTraceSupport.getValue()) { + if (InterpreterTraceSupport.getValue() && !quiet) { traceInterpreter("found oneImpl: ").string(targetMethod.toString()); if (goThroughPLT) { calleeFtnPtr = targetMethod.getNativeEntryPoint(); @@ -860,7 +862,7 @@ public static Object dispatchInvocation(InterpreterResolvedJavaMethod seedMethod if (!targetMethod.hasBytecodes() && !goThroughPLT && calleeFtnPtr.isNonNull()) { goThroughPLT = true; /* arguments to Log methods might have side-effects */ - if (InterpreterTraceSupport.getValue()) { + if (InterpreterTraceSupport.getValue() && !quiet) { traceInterpreter("cannot interpret ").string(targetMethod.toString()).string(" falling back to compiled version ").hex(calleeFtnPtr).newline(); } } @@ -874,7 +876,7 @@ public static Object dispatchInvocation(InterpreterResolvedJavaMethod seedMethod } /* arguments to Log methods might have side-effects */ - if (InterpreterOptions.InterpreterTraceSupport.getValue()) { + if (InterpreterOptions.InterpreterTraceSupport.getValue() && !quiet) { traceInterpreter(" ".repeat(Interpreter.logIndent.get())) .string(" -> calling (") .string(goThroughPLT ? "plt" : "interp").string(") ") diff --git a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentJDWP.java b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentJDWP.java index 446fe9325832..d06dbfa0ca53 100644 --- a/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentJDWP.java +++ b/substratevm/src/com.oracle.svm.jdwp.resident/src/com/oracle/svm/jdwp/resident/impl/ResidentJDWP.java @@ -1920,7 +1920,7 @@ static Result fromThrowable(Throwable throwable) { static Result ofInvoke(boolean isVirtual, InterpreterResolvedJavaMethod method, Object... args) { try { - return fromValue(InterpreterToVM.dispatchInvocation(method, args, isVirtual, false, false, false)); + return fromValue(InterpreterToVM.dispatchInvocation(method, args, isVirtual, false, false, false, false)); } catch (SemanticJavaException e) { return fromThrowable(e.getCause()); } catch (StackOverflowError | OutOfMemoryError error) { From 21ba056077dc1d89b14082a5fc49ec8e49b04c98 Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Sun, 28 Sep 2025 17:25:08 +0200 Subject: [PATCH 07/14] When enabled, let crema handle MethodHandle, MemberName, & LambdaForm In that mode re-enable a number of JDK code path that are usually substituted. --- .../svm/core/hub/crema/CremaSupport.java | 16 + .../svm/core/invoke/MethodHandleUtils.java | 12 + .../svm/core/invoke/ResolvedMember.java | 33 ++ .../Target_java_lang_invoke_MemberName.java | 11 + ...et_java_lang_invoke_BoundMethodHandle.java | 5 +- .../Target_java_lang_invoke_LambdaForm.java | 11 + .../Target_java_lang_invoke_MethodHandle.java | 34 ++ ..._java_lang_invoke_MethodHandleNatives.java | 67 ++-- ..._invoke_MethodHandleNatives_Constants.java | 57 +++ .../Target_java_lang_invoke_MethodType.java | 3 + .../InterpreterResolvedJavaField.java | 3 +- .../InterpreterResolvedJavaMethod.java | 23 +- .../oracle/svm/interpreter/CremaFeature.java | 11 + .../svm/interpreter/CremaSupportImpl.java | 327 ++++++++++++++++++ 14 files changed, 571 insertions(+), 42 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/ResolvedMember.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives_Constants.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java index 50801a746cb4..2657b7956005 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/crema/CremaSupport.java @@ -32,6 +32,7 @@ import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.registry.SymbolsSupport; +import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.espresso.classfile.ParserKlass; import com.oracle.svm.espresso.classfile.descriptors.ByteSequence; import com.oracle.svm.espresso.classfile.descriptors.Signature; @@ -39,6 +40,7 @@ import com.oracle.svm.espresso.classfile.descriptors.Type; import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @@ -48,6 +50,20 @@ public interface CremaSupport { int getAfterFieldsOffset(DynamicHub hub); + Target_java_lang_invoke_MemberName resolveMemberName(Target_java_lang_invoke_MemberName mn, Class caller); + + Object invokeBasic(Target_java_lang_invoke_MemberName memberName, Object methodHandle, Object[] args); + + Object linkToVirtual(Object[] args); + + Object linkToStatic(Object[] args); + + Object linkToSpecial(Object[] args); + + Object linkToInterface(Object[] args); + + Object getStaticStorage(ResolvedJavaField resolved); + interface CremaDispatchTable { int vtableLength(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/MethodHandleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/MethodHandleUtils.java index faa1d2916ee9..e78928706eae 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/MethodHandleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/MethodHandleUtils.java @@ -29,6 +29,7 @@ import java.lang.invoke.MethodHandle; import com.oracle.svm.core.AlwaysInline; +import com.oracle.svm.core.hub.RuntimeClassLoading; import sun.invoke.util.Wrapper; @@ -127,4 +128,15 @@ public static short shortUnbox(Object retVal, Class returnType) { throw shouldNotReachHere("Unexpected type for unbox function"); } } + + /** + * Returns the resolved member if runtime class loading is enabled. Otherwise, always returns + * {@code null}. + */ + public static ResolvedMember getResolvedMember(Target_java_lang_invoke_MemberName memberName) { + if (RuntimeClassLoading.isSupported()) { + return memberName.resolved; + } + return null; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/ResolvedMember.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/ResolvedMember.java new file mode 100644 index 000000000000..391c3184cac6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/ResolvedMember.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.invoke; + +/** + * This interface should be implemented by the possible resolution results of member names. + * + * @see Target_java_lang_invoke_MemberName#resolved + */ +public interface ResolvedMember { +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/Target_java_lang_invoke_MemberName.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/Target_java_lang_invoke_MemberName.java index ad1288c1185d..e1cd26183213 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/Target_java_lang_invoke_MemberName.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/Target_java_lang_invoke_MemberName.java @@ -34,6 +34,8 @@ 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.annotate.TargetElement; +import com.oracle.svm.core.hub.RuntimeClassLoading.WithRuntimeClassLoading; import com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives; import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.VMError; @@ -46,7 +48,16 @@ public final class Target_java_lang_invoke_MemberName { @Inject @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public MethodHandleIntrinsic intrinsic; + /** + * This is used by crema to store metadata for the resolved field or method. + */ + @Inject @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + @TargetElement(onlyWith = WithRuntimeClassLoading.class)// + public ResolvedMember resolved; + + @Alias public Class clazz; @Alias public String name; + @Alias public Object type; @Alias public int flags; @Alias public Object resolution; 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 cde553877850..6be26c3ffc5d 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 @@ -38,6 +38,7 @@ import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.fieldvaluetransformer.NewEmptyArrayFieldValueTransformer; +import com.oracle.svm.core.hub.RuntimeClassLoading.NoRuntimeClassLoading; /** * In the JDK implementation of method handles, each bound method handle is an instance of a @@ -73,7 +74,7 @@ final class Target_java_lang_invoke_BoundMethodHandle { * We hijack the species with no bound parameters for our implementation since it already inherits * from BoundMethodHandle and doesn't contain any superfluous members. */ -@TargetClass(className = "java.lang.invoke.SimpleMethodHandle") +@TargetClass(className = "java.lang.invoke.SimpleMethodHandle", onlyWith = NoRuntimeClassLoading.class) final class Target_java_lang_invoke_SimpleMethodHandle { /* * Since we represent all the bound method handle species with the basic one, the species data @@ -130,7 +131,7 @@ Target_java_lang_invoke_BoundMethodHandle copyWith(MethodType type, Target_java_ } /* Hardcoded species, needs a special case to avoid initialization */ -@TargetClass(className = "java.lang.invoke.BoundMethodHandle", innerClass = "Species_L") +@TargetClass(className = "java.lang.invoke.BoundMethodHandle", innerClass = "Species_L", onlyWith = NoRuntimeClassLoading.class) final class Target_java_lang_invoke_BoundMethodHandle_Species_L { @Substitute static Target_java_lang_invoke_BoundMethodHandle make(MethodType mt, Target_java_lang_invoke_LambdaForm lf, Object argL0) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_LambdaForm.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_LambdaForm.java index aa5a47e7fae2..9c800593a390 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_LambdaForm.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_LambdaForm.java @@ -30,6 +30,8 @@ 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.annotate.TargetElement; +import com.oracle.svm.core.hub.RuntimeClassLoading.NoRuntimeClassLoading; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.util.ReflectionUtil; @@ -42,10 +44,18 @@ public final class Target_java_lang_invoke_LambdaForm { @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = LambdaFormCacheTransformer.class)// volatile Object transformCache; + // isCompiled needs to be reset if vmentry is reset + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + boolean isCompiled; + @Alias native String lambdaName(); + @Alias + native void prepare(); + @Substitute + @TargetElement(onlyWith = NoRuntimeClassLoading.class) void compileToBytecode() { /* * Those lambda form types are required to be precompiled to bytecode during method handles @@ -62,6 +72,7 @@ void compileToBytecode() { */ @Substitute @SuppressWarnings("static-method") + @TargetElement(onlyWith = NoRuntimeClassLoading.class) private boolean forceInterpretation() { return true; } 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 74faf7e1a96e..f03f3552feb2 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 @@ -44,7 +44,11 @@ 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.annotate.TargetElement; import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; +import com.oracle.svm.core.hub.RuntimeClassLoading; +import com.oracle.svm.core.hub.RuntimeClassLoading.NoRuntimeClassLoading; +import com.oracle.svm.core.hub.crema.CremaSupport; import com.oracle.svm.core.invoke.MethodHandleUtils; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.reflect.SubstrateAccessor; @@ -83,6 +87,17 @@ final class Target_java_lang_invoke_MethodHandle { /* All MethodHandle.invoke* methods funnel through here. */ @Substitute(polymorphicSignature = true) Object invokeBasic(Object... args) throws Throwable { + if (RuntimeClassLoading.isSupported()) { + Target_java_lang_invoke_LambdaForm form = internalForm(); + Target_java_lang_invoke_MemberName vmentry = form.vmentry; + if (vmentry == null) { + // if the form comes from the image, its entry might have been reset + form.prepare(); + vmentry = form.vmentry; + assert vmentry != null; + } + return CremaSupport.singleton().invokeBasic(vmentry, this, args); + } Target_java_lang_invoke_MemberName memberName = internalMemberName(); Object ret; if (memberName != null) { @@ -125,21 +140,33 @@ Object invokeExact(Object... args) throws Throwable { @Substitute(polymorphicSignature = true) static Object linkToVirtual(Object... args) throws Throwable { + if (RuntimeClassLoading.isSupported()) { + return CremaSupport.singleton().linkToVirtual(args); + } return Util_java_lang_invoke_MethodHandle.linkTo(args); } @Substitute(polymorphicSignature = true) static Object linkToStatic(Object... args) throws Throwable { + if (RuntimeClassLoading.isSupported()) { + return CremaSupport.singleton().linkToStatic(args); + } return Util_java_lang_invoke_MethodHandle.linkTo(args); } @Substitute(polymorphicSignature = true) static Object linkToInterface(Object... args) throws Throwable { + if (RuntimeClassLoading.isSupported()) { + return CremaSupport.singleton().linkToInterface(args); + } return Util_java_lang_invoke_MethodHandle.linkTo(args); } @Substitute(polymorphicSignature = true) static Object linkToSpecial(Object... args) throws Throwable { + if (RuntimeClassLoading.isSupported()) { + return CremaSupport.singleton().linkToSpecial(args); + } return Util_java_lang_invoke_MethodHandle.linkTo(args); } @@ -153,6 +180,7 @@ static Object linkToNative(Object... args) throws Throwable { } @Substitute + @TargetElement(onlyWith = NoRuntimeClassLoading.class) void maybeCustomize() { /* * JDK 8 update 60 added an additional customization possibility for method handles. For all @@ -161,6 +189,7 @@ void maybeCustomize() { } @Delete + @TargetElement(onlyWith = NoRuntimeClassLoading.class) native void customize(); } @@ -173,6 +202,11 @@ static Object linkTo(Object... args) throws Throwable { } static Object invokeInternal(Target_java_lang_invoke_MemberName memberName, MethodType methodType, Object... args) throws Throwable { + /* + * This is never reached in the "crema" case since invokeBasic & linkTo* are instead + * redirected to CremaSupport. + */ + assert !RuntimeClassLoading.isSupported(); /* * The method handle may have been resolved at build time. If that is the case, the * SVM-specific information needed to perform the invoke is not stored in the handle yet, so 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 7318012b91d2..91b2495b5a57 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 @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.methodhandles; +import static com.oracle.svm.core.invoke.MethodHandleUtils.getResolvedMember; import static com.oracle.svm.core.util.VMError.shouldNotReachHere; import static com.oracle.svm.core.util.VMError.unsupportedFeature; @@ -45,8 +46,6 @@ 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; @@ -54,6 +53,7 @@ import com.oracle.svm.core.hub.RuntimeClassLoading; import com.oracle.svm.core.hub.RuntimeClassLoading.NoRuntimeClassLoading; import com.oracle.svm.core.hub.RuntimeClassLoading.WithRuntimeClassLoading; +import com.oracle.svm.core.hub.crema.CremaSupport; import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; @@ -63,6 +63,7 @@ import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.debug.GraalError; +import jdk.vm.ci.meta.ResolvedJavaField; import sun.invoke.util.VerifyAccess; /** @@ -129,7 +130,7 @@ private static void expand(Target_java_lang_invoke_MemberName self) { @Substitute private static long objectFieldOffset(Target_java_lang_invoke_MemberName self) { - if (self.reflectAccess == null && self.intrinsic == null) { + if (self.reflectAccess == null && self.intrinsic == null && getResolvedMember(self) == null) { throw new InternalError("Unresolved field"); } if (!self.isField() || self.isStatic()) { @@ -140,12 +141,15 @@ private static long objectFieldOffset(Target_java_lang_invoke_MemberName self) { if (self.intrinsic != null) { return -1L; } + if (RuntimeClassLoading.isSupported() && self.resolved != null) { + return ((ResolvedJavaField) self.resolved).getOffset(); + } return UnsafeFieldUtil.getFieldOffset(SubstrateUtil.cast(self.reflectAccess, Target_java_lang_reflect_Field.class)); } @Substitute private static long staticFieldOffset(Target_java_lang_invoke_MemberName self) { - if (self.reflectAccess == null && self.intrinsic == null) { + if (self.reflectAccess == null && self.intrinsic == null && getResolvedMember(self) == null) { throw new InternalError("Unresolved field"); } if (!self.isField() || !self.isStatic()) { @@ -155,17 +159,23 @@ private static long staticFieldOffset(Target_java_lang_invoke_MemberName self) { if (self.intrinsic != null) { return -1L; } + if (RuntimeClassLoading.isSupported() && self.resolved != null) { + return ((ResolvedJavaField) self.resolved).getOffset(); + } return UnsafeFieldUtil.getFieldOffset(SubstrateUtil.cast(self.reflectAccess, Target_java_lang_reflect_Field.class)); } @Substitute private static Object staticFieldBase(Target_java_lang_invoke_MemberName self) { - if (self.reflectAccess == null) { + if (self.reflectAccess == null && getResolvedMember(self) == null) { throw new InternalError("Unresolved field"); } if (!self.isField() || !self.isStatic()) { throw new InternalError("Static field required"); } + if (RuntimeClassLoading.isSupported() && self.resolved != null) { + return CremaSupport.singleton().getStaticStorage((ResolvedJavaField) self.resolved); + } Field field = (Field) self.reflectAccess; int layerNumber; if (ImageLayerBuildingSupport.buildingImageLayer()) { @@ -211,7 +221,7 @@ public static Target_java_lang_invoke_MemberName resolve(Target_java_lang_invoke throws LinkageError, ClassNotFoundException { Class declaringClass = self.getDeclaringClass(); Target_java_lang_invoke_MemberName resolved = Util_java_lang_invoke_MethodHandleNatives.resolve(self, caller, speculativeResolve); - assert resolved == null || resolved.reflectAccess != null || resolved.intrinsic != null; + assert resolved == null || resolved.reflectAccess != null || resolved.intrinsic != null || getResolvedMember(resolved) != null; if (resolved != null && resolved.reflectAccess != null && caller != null && !Util_java_lang_invoke_MethodHandleNatives.verifyAccess(declaringClass, resolved.reflectAccess.getDeclaringClass(), resolved.reflectAccess.getModifiers(), caller, lookupMode)) { @@ -319,14 +329,24 @@ 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 || self.intrinsic != null) { - return self; - } + throws LinkageError { Class declaringClass = self.getDeclaringClass(); if (declaringClass == null) { return null; } + if (RuntimeClassLoading.isSupported()) { + try { + return CremaSupport.singleton().resolveMemberName(self, caller); + } catch (Throwable t) { + if (speculativeResolve) { + return null; + } + throw t; + } + } + if (self.reflectAccess != null || self.intrinsic != null) { + return self; + } /* Intrinsic methods */ self.intrinsic = MethodHandleIntrinsicImpl.resolve(self); @@ -399,30 +419,3 @@ 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 @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; - - /** - * Constant pool reference-kind codes, as used by CONSTANT_MethodHandle CP entries. - */ - @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_MethodHandleNatives_Constants.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives_Constants.java new file mode 100644 index 000000000000..ad9d9af089de --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives_Constants.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.methodhandles; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "java.lang.invoke.MethodHandleNatives", innerClass = "Constants") +public final class Target_java_lang_invoke_MethodHandleNatives_Constants { + // Checkstyle: stop + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static int MN_IS_METHOD; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static int MN_IS_CONSTRUCTOR; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static int MN_IS_FIELD; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static int MN_IS_TYPE; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static int MN_CALLER_SENSITIVE; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static int MN_REFERENCE_KIND_SHIFT; + + /* + * Constant pool reference-kind codes, as used by CONSTANT_MethodHandle CP entries. + */ + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static byte REF_NONE; // null + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static byte REF_getField; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static byte REF_getStatic; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static byte REF_putField; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static byte REF_putStatic; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static byte REF_invokeVirtual; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static byte REF_invokeStatic; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static byte REF_invokeSpecial; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static byte REF_newInvokeSpecial; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public static byte REF_invokeInterface; + @Alias @RecomputeFieldValue(isFinal = true, kind = Kind.None) public 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_MethodType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodType.java index 70ca2a256850..faff8c5b5453 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 @@ -31,6 +31,8 @@ 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; +import com.oracle.svm.core.hub.RuntimeClassLoading.NoRuntimeClassLoading; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; @TargetClass(java.lang.invoke.MethodType.class) @@ -70,6 +72,7 @@ static void maybeCustomize(Target_java_lang_invoke_MethodHandle mh) { final class Target_java_lang_invoke_InvokerBytecodeGenerator { @SuppressWarnings("unused") @Substitute + @TargetElement(onlyWith = NoRuntimeClassLoading.class) static Target_java_lang_invoke_MemberName generateLambdaFormInterpreterEntryPoint(MethodType mt) { return null; /* Prevent runtime compilation of invokers */ } diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaField.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaField.java index 73b0ef9bcee8..e7a3deefdee5 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaField.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaField.java @@ -34,6 +34,7 @@ import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.registry.SymbolsSupport; +import com.oracle.svm.core.invoke.ResolvedMember; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; import com.oracle.svm.core.util.VMError; import com.oracle.svm.espresso.classfile.descriptors.Name; @@ -49,7 +50,7 @@ import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.UnresolvedJavaType; -public class InterpreterResolvedJavaField implements ResolvedJavaField, CremaFieldAccess { +public class InterpreterResolvedJavaField implements ResolvedJavaField, CremaFieldAccess, ResolvedMember { public static final InterpreterResolvedJavaField[] EMPTY_ARRAY = new InterpreterResolvedJavaField[0]; // Special offset values diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java index 6b4145b72f35..891b3d38dd36 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java @@ -46,7 +46,9 @@ import com.oracle.svm.core.FunctionPointerHolder; import com.oracle.svm.core.hub.RuntimeClassLoading; +import com.oracle.svm.core.hub.crema.CremaSupport; import com.oracle.svm.core.hub.registry.SymbolsSupport; +import com.oracle.svm.core.invoke.ResolvedMember; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.util.VMError; @@ -77,7 +79,7 @@ * Encapsulates resolved methods used under close-world assumptions, compiled and interpretable, but * also abstract methods for vtable calls. */ -public class InterpreterResolvedJavaMethod implements ResolvedJavaMethod, CremaMethodAccess { +public class InterpreterResolvedJavaMethod implements ResolvedJavaMethod, CremaMethodAccess, ResolvedMember { public static final InterpreterResolvedJavaMethod[] EMPTY_ARRAY = new InterpreterResolvedJavaMethod[0]; public static final LocalVariableTable EMPTY_LOCAL_VARIABLE_TABLE = new LocalVariableTable(new Local[0]); public static final ExceptionHandler[] EMPTY_EXCEPTION_HANDLERS = new ExceptionHandler[0]; @@ -744,6 +746,23 @@ public final void reprofile() { // endregion Unimplemented methods public static InterpreterResolvedJavaMethod fromMemberName(Target_java_lang_invoke_MemberName memberName) { - return toJVMCI((Executable) memberName.reflectAccess); + InterpreterResolvedJavaMethod invoker = (InterpreterResolvedJavaMethod) memberName.resolved; + if (invoker == null) { + Executable reflectInvoker = (Executable) memberName.reflectAccess; + if (reflectInvoker == null) { + /* + * This should only happen on first use of image-heap MemberNames. Those don't have + * a `resolved` target and their `reflectAccess` is reset to null. Unfortunately we + * don't have a caller class to use for access checks. + */ + CremaSupport.singleton().resolveMemberName(memberName, null); + invoker = (InterpreterResolvedJavaMethod) memberName.resolved; + assert invoker != null; + } else { + invoker = toJVMCI(reflectInvoker); + memberName.resolved = invoker; + } + } + return invoker; } } diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java index 4026f860ea1c..bd3beaedaee4 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java @@ -92,6 +92,7 @@ private static boolean assertionsEnabled() { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { + methodHandleSetup(); FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; try { enterVTableInterpreterStub = InterpreterStubSection.class.getMethod("enterVTableInterpreterStub", int.class, Pointer.class); @@ -101,6 +102,16 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { } } + private static void methodHandleSetup() { + /* + * Get java.lang.invoke.LambdaForm.LF_FAILED initialized since we don't support perf counter + * creation at run-time. See Target_jdk_internal_perf_PerfCounter. + */ + Class lfClass = ReflectionUtil.lookupClass("java.lang.invoke.LambdaForm"); + Method failedCompilationCounterMethod = ReflectionUtil.lookupMethod(lfClass, "failedCompilationCounter"); + ReflectionUtil.invokeMethod(failedCompilationCounterMethod, null); + } + @Override public void afterAnalysis(AfterAnalysisAccess access) { AnalysisUniverse aUniverse = ((FeatureImpl.AfterAnalysisAccessImpl) access).getUniverse(); diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java index af71b4d13749..5f486029c5ce 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java @@ -24,8 +24,23 @@ */ package com.oracle.svm.interpreter; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.MN_IS_CONSTRUCTOR; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.MN_IS_FIELD; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.MN_IS_METHOD; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.MN_REFERENCE_KIND_SHIFT; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_getField; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_getStatic; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_invokeInterface; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_invokeSpecial; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_invokeStatic; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_invokeVirtual; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_newInvokeSpecial; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_putField; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.REF_putStatic; +import static com.oracle.svm.espresso.shared.meta.SignaturePolymorphicIntrinsic.InvokeGeneric; import static com.oracle.svm.interpreter.InterpreterStubSection.getCremaStubForVTableIndex; +import java.lang.invoke.MethodType; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; @@ -50,7 +65,10 @@ import com.oracle.svm.core.hub.registry.AbstractClassRegistry; import com.oracle.svm.core.hub.registry.ClassRegistries; import com.oracle.svm.core.hub.registry.SymbolsSupport; +import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; +import com.oracle.svm.core.log.Log; import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.VMError; import com.oracle.svm.espresso.classfile.ConstantPool; import com.oracle.svm.espresso.classfile.JavaKind; @@ -59,6 +77,7 @@ import com.oracle.svm.espresso.classfile.ParserMethod; import com.oracle.svm.espresso.classfile.attributes.Attribute; import com.oracle.svm.espresso.classfile.attributes.ConstantValueAttribute; +import com.oracle.svm.espresso.classfile.descriptors.ByteSequence; import com.oracle.svm.espresso.classfile.descriptors.Name; import com.oracle.svm.espresso.classfile.descriptors.ParserSymbols; import com.oracle.svm.espresso.classfile.descriptors.Signature; @@ -66,6 +85,9 @@ import com.oracle.svm.espresso.classfile.descriptors.Type; import com.oracle.svm.espresso.classfile.descriptors.TypeSymbols; import com.oracle.svm.espresso.shared.meta.MethodHandleIntrinsics; +import com.oracle.svm.espresso.shared.meta.SignaturePolymorphicIntrinsic; +import com.oracle.svm.espresso.shared.resolver.CallSiteType; +import com.oracle.svm.espresso.shared.resolver.ResolvedCall; import com.oracle.svm.espresso.shared.vtable.MethodTableException; import com.oracle.svm.espresso.shared.vtable.PartialMethod; import com.oracle.svm.espresso.shared.vtable.PartialType; @@ -727,6 +749,12 @@ public Object getStaticStorage(Class cls, boolean primitives, int layerNum) { return ((InterpreterResolvedObjectType) DynamicHub.fromClass(cls).getInterpreterType()).getStaticStorage(primitives, layerNum); } + @Override + public Object getStaticStorage(ResolvedJavaField resolved) { + InterpreterResolvedJavaField interpreterField = (InterpreterResolvedJavaField) resolved; + return interpreterField.getDeclaringClass().getStaticStorage(resolved.getType().getJavaKind().isPrimitive(), interpreterField.getInstalledLayerNum()); + } + @Override public Object execute(ResolvedJavaMethod targetMethod, Object[] args) { return Interpreter.execute((InterpreterResolvedJavaMethod) targetMethod, args); @@ -741,4 +769,303 @@ public Object allocateInstance(ResolvedJavaType type) { public ResolvedJavaMethod findMethodHandleIntrinsic(ResolvedJavaMethod signaturePolymorphicMethod, Symbol signature) { return methodHandleIntrinsics.findIntrinsic((InterpreterResolvedJavaMethod) signaturePolymorphicMethod, signature, CremaRuntimeAccess.getInstance()); } + + @Override + public Target_java_lang_invoke_MemberName resolveMemberName(Target_java_lang_invoke_MemberName mn, Class caller) { + if (mn.resolved != null) { + return mn; + } + Class declaringClass = mn.clazz; + Object type = mn.type; + String name = mn.name; + Symbol symbolicName = SymbolsSupport.getNames().lookup(name); + if (symbolicName == null) { + if (mn.isField()) { + throw new NoSuchFieldError(name); + } else { + assert mn.isInvocable(); + throw new NoSuchMethodError(name); + } + } + if (declaringClass.isPrimitive()) { + return null; + } + InterpreterResolvedObjectType holder; + if (declaringClass.isArray()) { + holder = (InterpreterResolvedObjectType) DynamicHub.fromClass(Object.class).getInterpreterType(); + } else { + holder = (InterpreterResolvedObjectType) DynamicHub.fromClass(declaringClass).getInterpreterType(); + } + ByteSequence desc = asDescriptor(type); + boolean doAccessChecks = false; + // No constraints check on MemberName + boolean doConstraintsChecks = false; + InterpreterResolvedJavaType accessingType = null; + if (caller != null && !caller.isPrimitive()) { + accessingType = (InterpreterResolvedJavaType) DynamicHub.fromClass(caller).getInterpreterType(); + } + int refKind = mn.getReferenceKind(); + if (mn.isField()) { + Symbol t = SymbolsSupport.getTypes().lookupValidType(desc); + if (t == null) { + throw new NoSuchFieldError(name); + } + InterpreterResolvedJavaField field = CremaLinkResolver.resolveFieldSymbolOrThrow(CremaRuntimeAccess.getInstance(), accessingType, symbolicName, t, holder, doAccessChecks, + doConstraintsChecks); + plantResolvedField(mn, field, refKind); + return mn; + } + if (mn.isConstructor()) { + if (symbolicName != ParserSymbols.ParserNames._init_) { + throw new LinkageError(); + } + refKind = REF_invokeSpecial; + } else { + VMError.guarantee(mn.isMethod()); + } + SignaturePolymorphicIntrinsic mhMethodId = getSignaturePolymorphicIntrinsicID(holder, refKind, symbolicName); + + if (mhMethodId == InvokeGeneric) { + // Can not resolve InvokeGeneric, as we would miss the invoker and appendix. + throw new InternalError(); + } + Symbol sig = lookupSignature(desc, mhMethodId); + InterpreterResolvedJavaMethod m = CremaLinkResolver.resolveMethodSymbol(CremaRuntimeAccess.getInstance(), accessingType, symbolicName, sig, holder, holder.isInterface(), doAccessChecks, + doConstraintsChecks); + var resolvedCall = CremaLinkResolver.resolveCallSiteOrThrow(CremaRuntimeAccess.getInstance(), + accessingType, m, callSiteFromRefKind(refKind), holder); + plantResolvedMethod(mn, resolvedCall); + return mn; + } + + private static void plantResolvedMethod(Target_java_lang_invoke_MemberName mn, + ResolvedCall resolvedCall) { + int methodFlags = getMethodFlags(resolvedCall); + InterpreterResolvedJavaMethod target = resolvedCall.getResolvedMethod(); + mn.resolved = target; + mn.flags = methodFlags; + mn.clazz = target.getDeclaringClass().getJavaClass(); + } + + private static int getMethodFlags(ResolvedCall resolvedCall) { + InterpreterResolvedJavaMethod resolvedMethod = resolvedCall.getResolvedMethod(); + int flags = resolvedMethod.getModifiers(); + // MN_CALLER_SENSITIVE should be added for caller-sensitive methods GR-68603 + if (resolvedMethod.isConstructor() || resolvedMethod.isClassInitializer()) { + flags |= MN_IS_CONSTRUCTOR; + flags |= (REF_newInvokeSpecial << MN_REFERENCE_KIND_SHIFT); + return flags; + } + flags |= MN_IS_METHOD; + switch (resolvedCall.getCallKind()) { + case STATIC: + flags |= (REF_invokeStatic << MN_REFERENCE_KIND_SHIFT); + break; + case DIRECT: + flags |= (REF_invokeSpecial << MN_REFERENCE_KIND_SHIFT); + break; + case VTABLE_LOOKUP: + flags |= (REF_invokeVirtual << MN_REFERENCE_KIND_SHIFT); + break; + case ITABLE_LOOKUP: + flags |= (REF_invokeInterface << MN_REFERENCE_KIND_SHIFT); + break; + } + return flags; + } + + private static void plantResolvedField(Target_java_lang_invoke_MemberName mn, InterpreterResolvedJavaField field, int refKind) { + mn.resolved = field; + mn.flags = getFieldFlags(refKind, field); + mn.clazz = field.getDeclaringClass().getJavaClass(); + } + + private static int getFieldFlags(int refKind, InterpreterResolvedJavaField field) { + int res = field.getModifiers(); + boolean isSetter = (refKind <= REF_putStatic) && !(refKind <= REF_getStatic); + res |= MN_IS_FIELD | ((field.isStatic() ? REF_getStatic : REF_getField) << MN_REFERENCE_KIND_SHIFT); + if (isSetter) { + res += ((REF_putField - REF_getField) << MN_REFERENCE_KIND_SHIFT); + } + return res; + } + + private static Symbol lookupSignature(ByteSequence desc, SignaturePolymorphicIntrinsic iid) { + Symbol signature; + if (iid != null) { + signature = SymbolsSupport.getSignatures().getOrCreateValidSignature(desc); + } else { + signature = SymbolsSupport.getSignatures().lookupValidSignature(desc); + } + if (signature == null) { + throw new NoSuchMethodError(); + } + return signature; + } + + private static CallSiteType callSiteFromRefKind(int refKind) { + if (refKind == REF_invokeVirtual) { + return CallSiteType.Virtual; + } + if (refKind == REF_invokeStatic) { + return CallSiteType.Static; + } + if (refKind == REF_invokeSpecial || refKind == REF_newInvokeSpecial) { + return CallSiteType.Special; + } + if (refKind == REF_invokeInterface) { + return CallSiteType.Interface; + } + throw VMError.shouldNotReachHere("refKind: " + refKind); + } + + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+36/src/hotspot/share/prims/methodHandles.cpp#L735-L749") + private static SignaturePolymorphicIntrinsic getSignaturePolymorphicIntrinsicID(InterpreterResolvedObjectType resolutionKlass, int refKind, Symbol name) { + SignaturePolymorphicIntrinsic mhMethodId = null; + if (ParserKlass.isSignaturePolymorphicHolderType(resolutionKlass.getSymbolicType())) { + if (refKind == REF_invokeVirtual || + refKind == REF_invokeSpecial || + refKind == REF_invokeStatic) { + SignaturePolymorphicIntrinsic iid = SignaturePolymorphicIntrinsic.getId(name, resolutionKlass); + if (iid != null && + ((refKind == REF_invokeStatic) == (iid.isStaticSignaturePolymorphic()))) { + mhMethodId = iid; + } + } + } + return mhMethodId; + } + + private static ByteSequence asDescriptor(Object type) { + return switch (type) { + case MethodType mt -> methodTypeAsSignature(mt); + case Class c -> typeAsDescriptor(c); + case String s -> ByteSequence.create(s); + default -> throw VMError.shouldNotReachHere(type.getClass().toString()); + }; + } + + private static Symbol typeAsDescriptor(Class c) { + return ((InterpreterResolvedJavaType) DynamicHub.fromClass(c).getInterpreterType()).getSymbolicType(); + } + + private static ByteSequence methodTypeAsSignature(MethodType mt) { + Class returnType = mt.returnType(); + int len = 2; + for (int i = 0; i < mt.parameterCount(); i++) { + Class parameterType = mt.parameterType(i); + len += typeAsDescriptor(parameterType).length(); + } + Symbol returnDescriptor = typeAsDescriptor(returnType); + len += returnDescriptor.length(); + byte[] bytes = new byte[len]; + int pos = 0; + bytes[pos++] = '('; + for (int i = 0; i < mt.parameterCount(); i++) { + Class parameterType = mt.parameterType(i); + Symbol paramType = typeAsDescriptor(parameterType); + paramType.writeTo(bytes, pos); + pos += paramType.length(); + } + bytes[pos++] = ')'; + returnDescriptor.writeTo(bytes, pos); + pos += returnDescriptor.length(); + assert pos == bytes.length; + return ByteSequence.wrap(bytes); + } + + @Override + public Object invokeBasic(Target_java_lang_invoke_MemberName memberName, Object methodHandle, Object[] args) { + // This is AOT-compiled code calling MethodHandle.invokeBasic + InterpreterResolvedJavaMethod vmentry = InterpreterResolvedJavaMethod.fromMemberName(memberName); + Object[] basicArgs = new Object[args.length + 1]; + basicArgs[0] = methodHandle; + System.arraycopy(args, 0, basicArgs, 1, args.length); + logIntrinsic("[from compiled] invokeBasic ", vmentry, basicArgs); + try { + return InterpreterToVM.dispatchInvocation(vmentry, basicArgs, false, false, false, false, true); + } catch (SemanticJavaException e) { + throw uncheckedThrow(e.getCause()); + } + } + + @Override + public Object linkToVirtual(Object[] args) { + // This is AOT-compiled code calling MethodHandle.linkToVirtual + Target_java_lang_invoke_MemberName mnTarget = (Target_java_lang_invoke_MemberName) args[args.length - 1]; + InterpreterResolvedJavaMethod target = InterpreterResolvedJavaMethod.fromMemberName(mnTarget); + Object[] basicArgs = Arrays.copyOf(args, args.length - 1); + logIntrinsic("[from compiled] linkToVirtual ", target, basicArgs); + try { + return InterpreterToVM.dispatchInvocation(target, basicArgs, true, false, false, false, true); + } catch (SemanticJavaException e) { + throw uncheckedThrow(e.getCause()); + } + } + + @Override + public Object linkToStatic(Object[] args) { + // This is AOT-compiled code calling MethodHandle.linkToStatic + Target_java_lang_invoke_MemberName mnTarget = (Target_java_lang_invoke_MemberName) args[args.length - 1]; + InterpreterResolvedJavaMethod target = InterpreterResolvedJavaMethod.fromMemberName(mnTarget); + Object[] basicArgs = Arrays.copyOf(args, args.length - 1); + logIntrinsic("[from compiled] linkToStatic ", target, basicArgs); + try { + return InterpreterToVM.dispatchInvocation(target, basicArgs, false, false, false, false, true); + } catch (SemanticJavaException e) { + throw uncheckedThrow(e.getCause()); + } + } + + @Override + public Object linkToSpecial(Object[] args) { + // This is AOT-compiled code calling MethodHandle.linkToSpecial + Target_java_lang_invoke_MemberName mnTarget = (Target_java_lang_invoke_MemberName) args[args.length - 1]; + InterpreterResolvedJavaMethod target = InterpreterResolvedJavaMethod.fromMemberName(mnTarget); + Object[] basicArgs = Arrays.copyOf(args, args.length - 1); + logIntrinsic("[from compiled] linkToSpecial ", target, basicArgs); + try { + return InterpreterToVM.dispatchInvocation(target, basicArgs, false, false, false, false, true); + } catch (SemanticJavaException e) { + throw uncheckedThrow(e.getCause()); + } + } + + @Override + public Object linkToInterface(Object[] args) { + // This is AOT-compiled code calling MethodHandle.linkToInterface + Target_java_lang_invoke_MemberName mnTarget = (Target_java_lang_invoke_MemberName) args[args.length - 1]; + InterpreterResolvedJavaMethod target = InterpreterResolvedJavaMethod.fromMemberName(mnTarget); + Object[] basicArgs = Arrays.copyOf(args, args.length - 1); + logIntrinsic("[from compiled] linkToInterface ", target, basicArgs); + try { + return InterpreterToVM.dispatchInvocation(target, basicArgs, true, false, false, true, true); + } catch (SemanticJavaException e) { + throw uncheckedThrow(e.getCause()); + } + } + + private static void logIntrinsic(String value, InterpreterResolvedJavaMethod vmentry, Object[] basicArgs) { + if (!InterpreterOptions.InterpreterTraceSupport.getValue() || !InterpreterOptions.InterpreterTrace.getValue()) { + return; + } + Log.log().string(value).string(vmentry.toString()).string(", args="); + for (int i = 0; i < basicArgs.length; i++) { + Object arg = basicArgs[i]; + if (arg == null) { + Log.log().string("null"); + } else { + Log.log().string(arg.getClass().getName()); + } + if (i < basicArgs.length - 1) { + Log.log().string(", "); + } + } + Log.log().newline(); + } + + @SuppressWarnings("unchecked") + private static RuntimeException uncheckedThrow(Throwable throwable) throws E { + throw (E) throwable; + } } From 88d2e17c1a5153250fbe4d877c6ccdfb8ec09dd4 Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Sun, 28 Sep 2025 15:10:03 +0200 Subject: [PATCH 08/14] Add `InterpreterUtil.assertion` This helps avoid AssertionError in the interpreter. --- .../com/oracle/svm/interpreter/Interpreter.java | 2 +- .../svm/interpreter/InterpreterStubSection.java | 16 ++++++++-------- .../oracle/svm/interpreter/InterpreterUtil.java | 14 +++++++++++++- .../svm/interpreter/SemanticJavaException.java | 2 +- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java index b32425e45d94..aa4b602cf66e 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/Interpreter.java @@ -322,7 +322,7 @@ private static void initArguments(InterpreterFrame frame, InterpreterResolvedJav int receiverSlot = hasReceiver ? 1 : 0; int curSlot = 0; if (hasReceiver) { - assert arguments[0] != null : "null receiver in init arguments !"; + InterpreterUtil.assertion(arguments[0] != null, "null receiver in init arguments !"); Object receiver = arguments[0]; setLocalObject(frame, curSlot, receiver); curSlot += JavaKind.Object.getSlotCount(); diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubSection.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubSection.java index 771af737d50b..e95e009e2ad1 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubSection.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubSection.java @@ -278,35 +278,35 @@ private static Pointer enterHelper(InterpreterResolvedJavaMethod interpreterMeth switch (returnType.getJavaKind()) { case Boolean: - assert retVal instanceof Boolean; + InterpreterUtil.assertion(retVal instanceof Boolean, "invalid return type"); accessHelper.setGpReturn(enterData, ((Boolean) retVal) ? 1 : 0); break; case Byte: - assert retVal instanceof Byte; + InterpreterUtil.assertion(retVal instanceof Byte, "invalid return type"); accessHelper.setGpReturn(enterData, ((Byte) retVal).longValue()); break; case Short: - assert retVal instanceof Short; + InterpreterUtil.assertion(retVal instanceof Short, "invalid return type"); accessHelper.setGpReturn(enterData, ((Short) retVal).longValue()); break; case Char: - assert retVal instanceof Character; + InterpreterUtil.assertion(retVal instanceof Character, "invalid return type"); accessHelper.setGpReturn(enterData, ((Character) retVal).charValue()); break; case Int: - assert retVal instanceof Integer; + InterpreterUtil.assertion(retVal instanceof Integer, "invalid return type"); accessHelper.setGpReturn(enterData, ((Integer) retVal).longValue()); break; case Long: - assert retVal instanceof Long; + InterpreterUtil.assertion(retVal instanceof Long, "invalid return type"); accessHelper.setGpReturn(enterData, (Long) retVal); break; case Float: - assert retVal instanceof Float; + InterpreterUtil.assertion(retVal instanceof Float, "invalid return type"); accessHelper.setFpReturn(enterData, Float.floatToRawIntBits((float) retVal)); break; case Double: - assert retVal instanceof Double; + InterpreterUtil.assertion(retVal instanceof Double, "invalid return type"); accessHelper.setFpReturn(enterData, Double.doubleToRawLongBits((double) retVal)); break; case Object: diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterUtil.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterUtil.java index fe4d15927426..c8dfbc966545 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterUtil.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterUtil.java @@ -24,14 +24,20 @@ */ package com.oracle.svm.interpreter; -import com.oracle.svm.core.log.Log; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.log.Log; import com.oracle.svm.core.util.VMError; import com.oracle.svm.interpreter.metadata.MetadataUtil; public class InterpreterUtil { + private static final boolean assertionsEnabled; + static { + boolean status = false; + assert (status = true) == true; + assertionsEnabled = status; + } /** * Alternative to {@link VMError#guarantee(boolean, String, Object)} that avoids @@ -43,6 +49,12 @@ public static void guarantee(boolean condition, String simpleFormat, Object arg1 } } + public static void assertion(boolean condition, String message) { + if (assertionsEnabled && !condition) { + VMError.guarantee(condition, message); + } + } + /** * Build time logging. */ diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/SemanticJavaException.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/SemanticJavaException.java index f3b749670982..25cc2f5711ee 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/SemanticJavaException.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/SemanticJavaException.java @@ -46,7 +46,7 @@ public Throwable fillInStackTrace() { } public static RuntimeException raise(Throwable cause) { - assert cause != null && !(cause instanceof SemanticJavaException); + InterpreterUtil.assertion(cause != null && !(cause instanceof SemanticJavaException), "bad SemanticJavaException nesting"); throw new SemanticJavaException(cause); } } From 2cdb5553b58c22fc1ddafaf7af11c81000d2f5ea Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Tue, 14 Oct 2025 21:32:23 +0200 Subject: [PATCH 09/14] Fix CremaMethodAccess.toSymbol for signature with proxies --- .../interpreter/metadata/CremaMethodAccess.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java index 2b203bdb153b..43c814142195 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java @@ -88,14 +88,15 @@ static InterpreterResolvedJavaMethod toJVMCI(Executable executable) { */ name = SymbolsSupport.getNames().lookup(executable.getName()); } + // hidden classes and SVM stable proxy name contain a `.`, replace with a `+` StringBuilder sb = new StringBuilder(); sb.append('('); for (Class type : executable.getParameterTypes()) { - sb.append(type.descriptorString()); + sb.append(type.descriptorString().replace('.', '+')); } sb.append(')'); if (executable instanceof Method method) { - sb.append(method.getReturnType().descriptorString()); + sb.append(method.getReturnType().descriptorString().replace('.', '+')); } else { assert executable instanceof Constructor; sb.append('V'); @@ -105,14 +106,17 @@ static InterpreterResolvedJavaMethod toJVMCI(Executable executable) { } static Symbol toSymbol(InterpreterUnresolvedSignature jvmciSignature, SignatureSymbols signatures) { + // hidden classes and SVM stable proxy name contain a `.`, replace with a `+` StringBuilder sb = new StringBuilder(); sb.append('('); for (int i = 0; i < jvmciSignature.getParameterCount(false); i++) { - sb.append(jvmciSignature.getParameterType(i, null).getName()); + sb.append(jvmciSignature.getParameterType(i, null).getName().replace('.', '+')); } sb.append(')'); - sb.append(jvmciSignature.getReturnType(null).getName()); - return signatures.getOrCreateValidSignature(ByteSequence.create(sb.toString())); + sb.append(jvmciSignature.getReturnType(null).getName().replace('.', '+')); + Symbol symbol = signatures.getOrCreateValidSignature(ByteSequence.create(sb.toString())); + assert symbol != null : jvmciSignature; + return symbol; } static JavaType toJavaType(Symbol typeSymbol) { From 1ff0271ef1f6e7d1d03f3dfc6de1c8614486a089 Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Fri, 10 Oct 2025 09:25:53 +0200 Subject: [PATCH 10/14] InterpreterResolvedJavaMethod: make sure some members can't be null. --- .../metadata/InterpreterResolvedJavaMethod.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java index 891b3d38dd36..71606c6aaa76 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java @@ -171,13 +171,13 @@ private InterpreterResolvedJavaMethod(Symbol name, InterpreterResolvedObjectType declaringClass, InterpreterUnresolvedSignature signature, byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId, SignaturePolymorphicIntrinsic intrinsic) { - this.name = name; + this.name = MetadataUtil.requireNonNull(name); this.maxLocals = maxLocals; this.maxStackSize = maxStackSize; this.modifiers = modifiers; this.isSubstitutedNative = isSubstitutedNative; - this.declaringClass = declaringClass; - this.signature = signature; + this.declaringClass = MetadataUtil.requireNonNull(declaringClass); + this.signature = MetadataUtil.requireNonNull(signature); this.interpretedCode = code; this.exceptionHandlers = exceptionHandlers; this.lineNumberTable = lineNumberTable; @@ -198,10 +198,10 @@ private InterpreterResolvedJavaMethod(Symbol name, protected InterpreterResolvedJavaMethod(InterpreterResolvedObjectType declaringClass, ParserMethod m, int vtableIndex) { assert RuntimeClassLoading.isSupported(); - this.name = m.getName(); - this.signatureSymbol = m.getSignature(); + this.name = MetadataUtil.requireNonNull(m.getName()); + this.signatureSymbol = MetadataUtil.requireNonNull(m.getSignature()); - this.declaringClass = declaringClass; + this.declaringClass = MetadataUtil.requireNonNull(declaringClass); this.modifiers = m.getFlags() & Constants.JVM_RECOGNIZED_METHOD_MODIFIERS; this.signaturePolymorphic = (m.getFlags() & ACC_SIGNATURE_POLYMORPHIC) != 0; CodeAttribute codeAttribute = (CodeAttribute) m.getAttribute(CodeAttribute.NAME); From 7efc385924ed536dc026c056f23000451876923a Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Tue, 14 Oct 2025 21:55:19 +0200 Subject: [PATCH 11/14] InterpreterResolvedJavaMethod: Store internal flags and modifiers in an int --- .../InterpreterResolvedJavaMethod.java | 144 +++++++++++++----- .../metadata/serialization/Serializers.java | 10 +- .../BuildTimeInterpreterUniverse.java | 2 +- 3 files changed, 110 insertions(+), 46 deletions(-) diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java index 71606c6aaa76..fafb13ca7179 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java @@ -30,6 +30,7 @@ import static com.oracle.svm.espresso.classfile.Constants.ACC_STATIC; import static com.oracle.svm.espresso.classfile.Constants.ACC_SYNTHETIC; import static com.oracle.svm.espresso.classfile.Constants.ACC_VARARGS; +import static com.oracle.svm.espresso.classfile.Constants.JVM_RECOGNIZED_METHOD_MODIFIERS; import static com.oracle.svm.interpreter.metadata.Bytecodes.BREAKPOINT; import static com.oracle.svm.interpreter.metadata.CremaMethodAccess.toJVMCI; @@ -52,7 +53,6 @@ import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.espresso.classfile.Constants; import com.oracle.svm.espresso.classfile.JavaVersion; import com.oracle.svm.espresso.classfile.ParserMethod; import com.oracle.svm.espresso.classfile.attributes.CodeAttribute; @@ -80,6 +80,12 @@ * also abstract methods for vtable calls. */ public class InterpreterResolvedJavaMethod implements ResolvedJavaMethod, CremaMethodAccess, ResolvedMember { + /** + * This flag denotes a method that was originally native but was substituted by a non-native + * method. + */ + private static final int ACC_SUBSTITUTED_NATIVE = 0x80000000; + public static final InterpreterResolvedJavaMethod[] EMPTY_ARRAY = new InterpreterResolvedJavaMethod[0]; public static final LocalVariableTable EMPTY_LOCAL_VARIABLE_TABLE = new LocalVariableTable(new Local[0]); public static final ExceptionHandler[] EMPTY_EXCEPTION_HANDLERS = new ExceptionHandler[0]; @@ -94,9 +100,13 @@ public class InterpreterResolvedJavaMethod implements ResolvedJavaMethod, CremaM private final Symbol name; private final int maxLocals; private final int maxStackSize; - private final int modifiers; - private final boolean isSubstitutedNative; - private final boolean signaturePolymorphic; + /** + * Contains standard modifiers in the low 16 bits, and non-standard flags in the upper 16 bits. + * + * @see #ACC_SUBSTITUTED_NATIVE + * @see com.oracle.svm.espresso.classfile.Constants + */ + private final int flags; @Platforms(Platform.HOSTED_ONLY.class) // private ResolvedJavaMethod originalMethod; @@ -125,7 +135,7 @@ public InlinedBy(InterpreterResolvedJavaMethod holder, Set name, int maxLocals, int maxStackSize, int modifiers, InterpreterResolvedObjectType declaringClass, - InterpreterUnresolvedSignature signature, boolean isSubstitutedNative, + private InterpreterResolvedJavaMethod(ResolvedJavaMethod originalMethod, Symbol name, int maxLocals, int maxStackSize, int flags, + InterpreterResolvedObjectType declaringClass, InterpreterUnresolvedSignature signature, Symbol signatureSymbol, byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { - this(name, maxLocals, maxStackSize, modifiers, isSubstitutedNative, declaringClass, signature, code, exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, - gotOffset, - enterStubOffset, methodId, null); + this.name = MetadataUtil.requireNonNull(name); + this.maxLocals = maxLocals; + this.maxStackSize = maxStackSize; + this.flags = flags; + this.declaringClass = MetadataUtil.requireNonNull(declaringClass); + this.signature = MetadataUtil.requireNonNull(signature); + this.signatureSymbol = MetadataUtil.requireNonNull(signatureSymbol); + this.interpretedCode = code; + this.exceptionHandlers = exceptionHandlers; + this.lineNumberTable = lineNumberTable; + this.localVariableTable = localVariableTable; + this.nativeEntryPoint = nativeEntryPoint; + this.vtableIndex = vtableIndex; + this.gotOffset = gotOffset; + this.enterStubOffset = enterStubOffset; + this.methodId = methodId; + this.inlinedBy = new InlinedBy(this, new HashSet<>()); + this.intrinsic = null; + this.originalMethod = originalMethod; - this.needMethodBody = false; - this.inlinedBy = new InterpreterResolvedJavaMethod.InlinedBy(this, new HashSet<>()); } - private InterpreterResolvedJavaMethod(Symbol name, - int maxLocals, int maxStackSize, - int modifiers, boolean isSubstitutedNative, - InterpreterResolvedObjectType declaringClass, InterpreterUnresolvedSignature signature, + // Used at run-time for deserialization + private InterpreterResolvedJavaMethod(Symbol name, int maxLocals, int maxStackSize, int flags, + InterpreterResolvedObjectType declaringClass, InterpreterUnresolvedSignature signature, Symbol signatureSymbol, byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, - ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId, SignaturePolymorphicIntrinsic intrinsic) { + ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { this.name = MetadataUtil.requireNonNull(name); this.maxLocals = maxLocals; this.maxStackSize = maxStackSize; - this.modifiers = modifiers; - this.isSubstitutedNative = isSubstitutedNative; + this.flags = flags; this.declaringClass = MetadataUtil.requireNonNull(declaringClass); this.signature = MetadataUtil.requireNonNull(signature); + this.signatureSymbol = MetadataUtil.requireNonNull(signatureSymbol); this.interpretedCode = code; this.exceptionHandlers = exceptionHandlers; this.lineNumberTable = lineNumberTable; this.localVariableTable = localVariableTable; - this.nativeEntryPoint = nativeEntryPoint; this.vtableIndex = vtableIndex; this.gotOffset = gotOffset; this.enterStubOffset = enterStubOffset; this.methodId = methodId; this.inlinedBy = new InlinedBy(this, new HashSet<>()); + this.intrinsic = null; + } - this.signatureSymbol = CremaMethodAccess.toSymbol(signature, SymbolsSupport.getSignatures()); - this.intrinsic = intrinsic; - this.signaturePolymorphic = ParserMethod.isDeclaredSignaturePolymorphic(declaringClass.getSymbolicType(), signatureSymbol, getOriginalModifiers(modifiers, isSubstitutedNative), - JavaVersion.HOST_VERSION); + // Used at run-time for signature-polymorphic instantiation + private InterpreterResolvedJavaMethod(Symbol name, int maxLocals, int flags, + InterpreterResolvedObjectType declaringClass, InterpreterUnresolvedSignature signature, Symbol signatureSymbol, + int vtableIndex, int gotOffset, int enterStubOffset, int methodId, SignaturePolymorphicIntrinsic intrinsic) { + this.name = MetadataUtil.requireNonNull(name); + this.maxLocals = maxLocals; + this.maxStackSize = 0; + this.flags = flags; + this.declaringClass = MetadataUtil.requireNonNull(declaringClass); + this.signature = MetadataUtil.requireNonNull(signature); + this.signatureSymbol = MetadataUtil.requireNonNull(signatureSymbol); + // not bytecode-interpretable + this.interpretedCode = null; + this.exceptionHandlers = null; + this.lineNumberTable = null; + this.localVariableTable = null; + this.nativeEntryPoint = null; + this.vtableIndex = vtableIndex; + this.gotOffset = gotOffset; + this.enterStubOffset = enterStubOffset; + this.methodId = methodId; + this.inlinedBy = null; + this.intrinsic = MetadataUtil.requireNonNull(intrinsic); } + // Used at run-time for the crema sub-class protected InterpreterResolvedJavaMethod(InterpreterResolvedObjectType declaringClass, ParserMethod m, int vtableIndex) { assert RuntimeClassLoading.isSupported(); this.name = MetadataUtil.requireNonNull(m.getName()); this.signatureSymbol = MetadataUtil.requireNonNull(m.getSignature()); - this.declaringClass = MetadataUtil.requireNonNull(declaringClass); - this.modifiers = m.getFlags() & Constants.JVM_RECOGNIZED_METHOD_MODIFIERS; - this.signaturePolymorphic = (m.getFlags() & ACC_SIGNATURE_POLYMORPHIC) != 0; + this.flags = m.getFlags(); + assert (flags & ACC_SUBSTITUTED_NATIVE) == 0; CodeAttribute codeAttribute = (CodeAttribute) m.getAttribute(CodeAttribute.NAME); if (codeAttribute != null) { this.maxLocals = codeAttribute.getMaxLocals(); @@ -224,37 +267,51 @@ protected InterpreterResolvedJavaMethod(InterpreterResolvedObjectType declaringC this.gotOffset = -2 /* -GOT_NO_ENTRY */; this.enterStubOffset = EST_NO_ENTRY; this.methodId = UNKNOWN_METHOD_ID; - this.inlinedBy = new InlinedBy(this, new HashSet<>()); - this.isSubstitutedNative = false; + this.inlinedBy = null; this.intrinsic = null; } @VisibleForSerialization - public static InterpreterResolvedJavaMethod create(String name, int maxLocals, int maxStackSize, int modifiers, InterpreterResolvedObjectType declaringClass, + public static InterpreterResolvedJavaMethod createForDeserialization(String name, int maxLocals, int maxStackSize, int flags, InterpreterResolvedObjectType declaringClass, InterpreterUnresolvedSignature signature, byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { Symbol nameSymbol = SymbolsSupport.getNames().getOrCreate(name); - return new InterpreterResolvedJavaMethod(nameSymbol, maxLocals, maxStackSize, modifiers, false, declaringClass, signature, code, - exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId, null); + Symbol signatureSymbol = CremaMethodAccess.toSymbol(signature, SymbolsSupport.getSignatures()); + return new InterpreterResolvedJavaMethod(nameSymbol, maxLocals, maxStackSize, flags, declaringClass, signature, signatureSymbol, code, + exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId); } // Only called during universe building @Platforms(Platform.HOSTED_ONLY.class) - public static InterpreterResolvedJavaMethod create(ResolvedJavaMethod originalMethod, String name, int maxLocals, int maxStackSize, int modifiers, + public static InterpreterResolvedJavaMethod createAtBuildTime(ResolvedJavaMethod originalMethod, String name, int maxLocals, int maxStackSize, int modifiers, InterpreterResolvedObjectType declaringClass, InterpreterUnresolvedSignature signature, boolean isSubstitutedNative, byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { Symbol nameSymbol = SymbolsSupport.getNames().getOrCreate(name); - return new InterpreterResolvedJavaMethod(originalMethod, nameSymbol, maxLocals, maxStackSize, modifiers, declaringClass, signature, isSubstitutedNative, code, + Symbol signatureSymbol = CremaMethodAccess.toSymbol(signature, SymbolsSupport.getSignatures()); + int flags = createFlags(modifiers, declaringClass, signatureSymbol, isSubstitutedNative, originalMethod); + return new InterpreterResolvedJavaMethod(originalMethod, nameSymbol, maxLocals, maxStackSize, flags, declaringClass, signature, signatureSymbol, code, exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId); } + @Platforms(Platform.HOSTED_ONLY.class) + private static int createFlags(int modifiers, InterpreterResolvedObjectType declaringClass, Symbol signatureSymbol, boolean isSubstitutedNative, ResolvedJavaMethod originalMethod) { + int newModifiers = modifiers; + if (ParserMethod.isDeclaredSignaturePolymorphic(declaringClass.getSymbolicType(), signatureSymbol, getOriginalModifiers(modifiers, isSubstitutedNative), JavaVersion.HOST_VERSION)) { + newModifiers |= ACC_SIGNATURE_POLYMORPHIC; + } + if (isSubstitutedNative) { + newModifiers |= ACC_SUBSTITUTED_NATIVE; + } + return newModifiers; + } + @Override public final boolean isDeclaredSignaturePolymorphic() { // Note: might not be true for the instantiation of polymorphic signature intrinsics. - return signaturePolymorphic; + return (flags & ACC_SIGNATURE_POLYMORPHIC) != 0; } @Override @@ -263,8 +320,9 @@ public final InterpreterResolvedJavaMethod createSignaturePolymorphicIntrinsic(S assert iid != null; assert intrinsic == null; int newModifiers; + boolean isSubstitutedNative = (flags & ACC_SUBSTITUTED_NATIVE) != 0; if (iid == SignaturePolymorphicIntrinsic.InvokeGeneric) { - newModifiers = getOriginalModifiers(modifiers, isSubstitutedNative) & ~ACC_VARARGS; + newModifiers = getOriginalModifiers(flags, isSubstitutedNative) & ~(ACC_VARARGS | ACC_SUBSTITUTED_NATIVE | ACC_SIGNATURE_POLYMORPHIC); } else { newModifiers = ACC_NATIVE | ACC_SYNTHETIC | ACC_FINAL; if (iid.isStaticSignaturePolymorphic()) { @@ -273,9 +331,8 @@ public final InterpreterResolvedJavaMethod createSignaturePolymorphicIntrinsic(S } assert Modifier.isNative(newModifiers); InterpreterUnresolvedSignature jvmciSignature = CremaMethodAccess.toJVMCI(newSignature, SymbolsSupport.getTypes()); - return new InterpreterResolvedJavaMethod(name, jvmciSignature.getParameterCount(true), 0, newModifiers, isSubstitutedNative, declaringClass, jvmciSignature, - null, null, null, null, // not bytecode-interpretable - null, vtableIndex, gotOffset, enterStubOffset, methodId, iid); + return new InterpreterResolvedJavaMethod(name, jvmciSignature.getParameterCount(true), newModifiers, declaringClass, jvmciSignature, newSignature, + vtableIndex, gotOffset, enterStubOffset, methodId, iid); } private static int getOriginalModifiers(int modifiers, boolean isSubstitutedNative) { @@ -424,7 +481,11 @@ public final LocalVariableTable getLocalVariableTable() { @Override public final int getModifiers() { - return modifiers; + return flags & JVM_RECOGNIZED_METHOD_MODIFIERS; + } + + public int getFlags() { + return flags; } @Override @@ -594,6 +655,9 @@ public final boolean isInterpreterExecutable() { } public final Set getInlinedBy() { + if (inlinedBy == null) { + return Set.of(); + } return inlinedBy.inliners; } diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serializers.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serializers.java index 686ce9ad97fe..844ff628a62b 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serializers.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/serialization/Serializers.java @@ -640,7 +640,7 @@ public static ValueSerializer> newReferenceConstantSerializ String name = context.readReference(in); int maxLocals = LEB128.readUnsignedInt(in); int maxStackSize = LEB128.readUnsignedInt(in); - int modifiers = LEB128.readUnsignedInt(in); + int flags = LEB128.readUnsignedInt(in); InterpreterResolvedObjectType declaringClass = context.readReference(in); InterpreterUnresolvedSignature signature = context.readReference(in); byte[] code = context.readReference(in); @@ -654,14 +654,14 @@ public static ValueSerializer> newReferenceConstantSerializ int enterStubOffset = LEB128.readUnsignedInt(in); int methodId = LEB128.readUnsignedInt(in); - return InterpreterResolvedJavaMethod.create(name, maxLocals, maxStackSize, modifiers, declaringClass, signature, code, exceptionHandlers, lineNumberTable, localVariableTable, - nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId); + return InterpreterResolvedJavaMethod.createForDeserialization(name, maxLocals, maxStackSize, flags, declaringClass, signature, code, exceptionHandlers, lineNumberTable, + localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId); }, (context, out, value) -> { String name = value.getName(); int maxLocals = value.getMaxLocals(); int maxStackSize = value.getMaxStackSize(); - int modifiers = value.getModifiers(); + int flags = value.getFlags(); InterpreterResolvedObjectType declaringClass = value.getDeclaringClass(); InterpreterUnresolvedSignature signature = value.getSignature(); byte[] code = value.getInterpretedCode(); @@ -682,7 +682,7 @@ public static ValueSerializer> newReferenceConstantSerializ context.writeReference(out, name); LEB128.writeUnsignedInt(out, maxLocals); LEB128.writeUnsignedInt(out, maxStackSize); - LEB128.writeUnsignedInt(out, modifiers); + LEB128.writeUnsignedInt(out, flags); context.writeReference(out, declaringClass); context.writeReference(out, signature); context.writeReference(out, code); diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java index 764baac6a5af..7dae4c745b76 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeInterpreterUniverse.java @@ -218,7 +218,7 @@ public static InterpreterResolvedJavaMethod createResolveJavaMethod(ResolvedJava } LineNumberTable lineNumberTable = originalMethod.getLineNumberTable(); - return InterpreterResolvedJavaMethod.create( + return InterpreterResolvedJavaMethod.createAtBuildTime( originalMethod, name, maxLocals, From aa79ad038a1d45b51ed2946217385a87db7851d2 Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Tue, 14 Oct 2025 22:10:11 +0200 Subject: [PATCH 12/14] Crema: detect caller sensitive methods --- .../metadata/InterpreterResolvedJavaMethod.java | 13 +++++++++++++ .../oracle/svm/interpreter/CremaSupportImpl.java | 5 ++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java index fafb13ca7179..23bfaaf733a1 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.interpreter.metadata; +import static com.oracle.svm.espresso.classfile.Constants.ACC_CALLER_SENSITIVE; import static com.oracle.svm.espresso.classfile.Constants.ACC_FINAL; import static com.oracle.svm.espresso.classfile.Constants.ACC_NATIVE; import static com.oracle.svm.espresso.classfile.Constants.ACC_SIGNATURE_POLYMORPHIC; @@ -42,6 +43,7 @@ import java.util.Set; import java.util.function.Function; +import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -63,6 +65,7 @@ import com.oracle.svm.espresso.shared.meta.SignaturePolymorphicIntrinsic; import com.oracle.svm.espresso.shared.vtable.PartialMethod; import com.oracle.svm.interpreter.metadata.serialization.VisibleForSerialization; +import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.word.Word; import jdk.vm.ci.meta.Constant; @@ -80,6 +83,9 @@ * also abstract methods for vtable calls. */ public class InterpreterResolvedJavaMethod implements ResolvedJavaMethod, CremaMethodAccess, ResolvedMember { + @Platforms(Platform.HOSTED_ONLY.class)// + @SuppressWarnings("unchecked") // + private static final Class CALLER_SENSITIVE_CLASS = (Class) ReflectionUtil.lookupClass("jdk.internal.reflect.CallerSensitive"); /** * This flag denotes a method that was originally native but was substituted by a non-native * method. @@ -305,9 +311,16 @@ private static int createFlags(int modifiers, InterpreterResolvedObjectType decl if (isSubstitutedNative) { newModifiers |= ACC_SUBSTITUTED_NATIVE; } + if (AnnotationAccess.isAnnotationPresent(originalMethod, CALLER_SENSITIVE_CLASS)) { + newModifiers |= ACC_CALLER_SENSITIVE; + } return newModifiers; } + public final boolean isCallerSensitive() { + return (flags & ACC_CALLER_SENSITIVE) != 0; + } + @Override public final boolean isDeclaredSignaturePolymorphic() { // Note: might not be true for the instantiation of polymorphic signature intrinsics. diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java index 5f486029c5ce..a0bac6df8b6f 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaSupportImpl.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.interpreter; +import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.MN_CALLER_SENSITIVE; import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.MN_IS_CONSTRUCTOR; import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.MN_IS_FIELD; import static com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives_Constants.MN_IS_METHOD; @@ -850,7 +851,9 @@ private static void plantResolvedMethod(Target_java_lang_invoke_MemberName mn, private static int getMethodFlags(ResolvedCall resolvedCall) { InterpreterResolvedJavaMethod resolvedMethod = resolvedCall.getResolvedMethod(); int flags = resolvedMethod.getModifiers(); - // MN_CALLER_SENSITIVE should be added for caller-sensitive methods GR-68603 + if (resolvedMethod.isCallerSensitive()) { + flags |= MN_CALLER_SENSITIVE; + } if (resolvedMethod.isConstructor() || resolvedMethod.isClassInitializer()) { flags |= MN_IS_CONSTRUCTOR; flags |= (REF_newInvokeSpecial << MN_REFERENCE_KIND_SHIFT); From 5e4607dd106b80b9ccfc18a71a7d5254470f5be6 Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Thu, 30 Oct 2025 09:51:56 +0100 Subject: [PATCH 13/14] Select exactly the right method for interpreter methods When compiling with ECJ there are other methods in those classes. --- .../svm/interpreter/InterpreterFeature.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFeature.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFeature.java index 8d9707567f35..f3f61e8f711f 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFeature.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterFeature.java @@ -43,7 +43,9 @@ import org.graalvm.word.Pointer; import org.graalvm.word.WordBase; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.InvalidMethodPointerHandler; import com.oracle.svm.core.ParsingReason; import com.oracle.svm.core.Uninterruptible; @@ -54,10 +56,13 @@ import com.oracle.svm.core.interpreter.InterpreterSupport; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.espresso.shared.meta.SignaturePolymorphicIntrinsic; import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.interpreter.debug.DebuggerEventsFeature; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.util.JVMCIReflectionUtil; import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.api.replacements.Fold; @@ -198,7 +203,11 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; BuildTimeInterpreterUniverse.freshSingletonInstance(); - AnalysisMethod interpreterRoot = accessImpl.getMetaAccess().lookupJavaType(Interpreter.Root.class).getDeclaredMethods(false)[0]; + AnalysisMetaAccess metaAccess = accessImpl.getMetaAccess(); + + AnalysisType interpreterRootType = metaAccess.lookupJavaType(Interpreter.Root.class); + AnalysisMethod interpreterRoot = (AnalysisMethod) JVMCIReflectionUtil.getDeclaredMethod(metaAccess, interpreterRootType, "executeBodyFromBCI", + InterpreterFrame.class, InterpreterResolvedJavaMethod.class, int.class, int.class, boolean.class); LocalVariableTable interpreterVariableTable = interpreterRoot.getLocalVariableTable(); int interpreterMethodSlot = findLocalSlotByName("method", interpreterVariableTable.getLocalsAt(0)); // parameter @@ -206,7 +215,9 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { // Local variable, search all locals. int bciSlot = findLocalSlotByName("curBCI", interpreterVariableTable.getLocals()); - AnalysisMethod intrinsicRoot = accessImpl.getMetaAccess().lookupJavaType(Interpreter.IntrinsicRoot.class).getDeclaredMethods(false)[0]; + AnalysisType intrinsicRootType = metaAccess.lookupJavaType(Interpreter.IntrinsicRoot.class); + AnalysisMethod intrinsicRoot = (AnalysisMethod) JVMCIReflectionUtil.getDeclaredMethod(metaAccess, intrinsicRootType, "execute", + InterpreterFrame.class, InterpreterResolvedJavaMethod.class, SignaturePolymorphicIntrinsic.class, boolean.class); LocalVariableTable intrinsicVariableTable = intrinsicRoot.getLocalVariableTable(); int intrinsicMethodSlot = findLocalSlotByName("method", intrinsicVariableTable.getLocalsAt(0)); // parameter @@ -222,7 +233,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { SubstrateCompilationDirectives.singleton().registerFrameInformationRequired(intrinsicRoot); Method leaveMethod = ReflectionUtil.lookupMethod(InterpreterStubSection.class, "leaveInterpreterStub", CFunctionPointer.class, Pointer.class, long.class, long.class); - leaveStub = accessImpl.getMetaAccess().lookupJavaMethod(leaveMethod); + leaveStub = metaAccess.lookupJavaMethod(leaveMethod); accessImpl.registerAsRoot(leaveStub, true, "low level entry point"); } From 32ae9625beedfb74e0339a8997258bab86e613cb Mon Sep 17 00:00:00 2001 From: Gilles Duboscq Date: Thu, 30 Oct 2025 12:00:47 +0100 Subject: [PATCH 14/14] Make CremaMethodAccess runtime-only --- .../metadata/CremaMethodAccess.java | 15 --------------- .../InterpreterResolvedJavaMethod.java | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java index 43c814142195..598df1c099d5 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/CremaMethodAccess.java @@ -32,7 +32,6 @@ import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.registry.SymbolsSupport; import com.oracle.svm.espresso.classfile.attributes.LineNumberTableAttribute; -import com.oracle.svm.espresso.classfile.descriptors.ByteSequence; import com.oracle.svm.espresso.classfile.descriptors.Name; import com.oracle.svm.espresso.classfile.descriptors.ParserSymbols; import com.oracle.svm.espresso.classfile.descriptors.Signature; @@ -105,20 +104,6 @@ static InterpreterResolvedJavaMethod toJVMCI(Executable executable) { return holder.lookupMethod(name, signature); } - static Symbol toSymbol(InterpreterUnresolvedSignature jvmciSignature, SignatureSymbols signatures) { - // hidden classes and SVM stable proxy name contain a `.`, replace with a `+` - StringBuilder sb = new StringBuilder(); - sb.append('('); - for (int i = 0; i < jvmciSignature.getParameterCount(false); i++) { - sb.append(jvmciSignature.getParameterType(i, null).getName().replace('.', '+')); - } - sb.append(')'); - sb.append(jvmciSignature.getReturnType(null).getName().replace('.', '+')); - Symbol symbol = signatures.getOrCreateValidSignature(ByteSequence.create(sb.toString())); - assert symbol != null : jvmciSignature; - return symbol; - } - static JavaType toJavaType(Symbol typeSymbol) { if (TypeSymbols.isPrimitive(typeSymbol)) { return InterpreterResolvedPrimitiveType.fromKind(JavaKind.fromPrimitiveOrVoidTypeChar((char) typeSymbol.byteAt(0))); diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java index 23bfaaf733a1..36c5d7380a8f 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterResolvedJavaMethod.java @@ -58,6 +58,7 @@ import com.oracle.svm.espresso.classfile.JavaVersion; import com.oracle.svm.espresso.classfile.ParserMethod; import com.oracle.svm.espresso.classfile.attributes.CodeAttribute; +import com.oracle.svm.espresso.classfile.descriptors.ByteSequence; import com.oracle.svm.espresso.classfile.descriptors.Name; import com.oracle.svm.espresso.classfile.descriptors.ParserSymbols; import com.oracle.svm.espresso.classfile.descriptors.Signature; @@ -283,7 +284,7 @@ public static InterpreterResolvedJavaMethod createForDeserialization(String name byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { Symbol nameSymbol = SymbolsSupport.getNames().getOrCreate(name); - Symbol signatureSymbol = CremaMethodAccess.toSymbol(signature, SymbolsSupport.getSignatures()); + Symbol signatureSymbol = toSymbol(signature); return new InterpreterResolvedJavaMethod(nameSymbol, maxLocals, maxStackSize, flags, declaringClass, signature, signatureSymbol, code, exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId); } @@ -296,12 +297,26 @@ public static InterpreterResolvedJavaMethod createAtBuildTime(ResolvedJavaMethod byte[] code, ExceptionHandler[] exceptionHandlers, LineNumberTable lineNumberTable, LocalVariableTable localVariableTable, ReferenceConstant nativeEntryPoint, int vtableIndex, int gotOffset, int enterStubOffset, int methodId) { Symbol nameSymbol = SymbolsSupport.getNames().getOrCreate(name); - Symbol signatureSymbol = CremaMethodAccess.toSymbol(signature, SymbolsSupport.getSignatures()); + Symbol signatureSymbol = toSymbol(signature); int flags = createFlags(modifiers, declaringClass, signatureSymbol, isSubstitutedNative, originalMethod); return new InterpreterResolvedJavaMethod(originalMethod, nameSymbol, maxLocals, maxStackSize, flags, declaringClass, signature, signatureSymbol, code, exceptionHandlers, lineNumberTable, localVariableTable, nativeEntryPoint, vtableIndex, gotOffset, enterStubOffset, methodId); } + private static Symbol toSymbol(InterpreterUnresolvedSignature jvmciSignature) { + // hidden classes and SVM stable proxy name contain a `.`, replace with a `+` + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (int i = 0; i < jvmciSignature.getParameterCount(false); i++) { + sb.append(jvmciSignature.getParameterType(i, null).getName().replace('.', '+')); + } + sb.append(')'); + sb.append(jvmciSignature.getReturnType(null).getName().replace('.', '+')); + Symbol symbol = SymbolsSupport.getSignatures().getOrCreateValidSignature(ByteSequence.create(sb.toString())); + assert symbol != null : jvmciSignature; + return symbol; + } + @Platforms(Platform.HOSTED_ONLY.class) private static int createFlags(int modifiers, InterpreterResolvedObjectType declaringClass, Symbol signatureSymbol, boolean isSubstitutedNative, ResolvedJavaMethod originalMethod) { int newModifiers = modifiers;