From f4334b3cf630cea3758ef10e3f2f5384381a48ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alfonso=C2=B2=20Peterssen?= Date: Fri, 18 Jul 2025 14:21:50 +0200 Subject: [PATCH] Implement Crema runtime constant pool. Track CP tags in the interpreter CP. Add metadata serialization support for parser CPs. Derive partial parser/symbolic constant pool from AOT-compiled types. Implement Crema's LinkResolver and RuntimeAccess. Use singleton instance of CremaRuntimeAccess. Implement RuntimeConstantPool. Add support for constant pools derived from JVMCI data-structures. Fix serialization of two-slots CP entries. Throw on missing/unresolved CP entries and provide a hint of the missing type/field/method. Add note about deadlocks caused by breakpoints hit during class resolution. --- .../classfile/ParserConstantPool.java | 68 ++++++ .../descriptors/SignatureSymbols.java | 11 + .../espresso/shared/meta/RuntimeAccess.java | 17 +- .../metadata/InterpreterConstantPool.java | 140 ++++++++++-- .../InterpreterResolvedObjectType.java | 57 ++++- .../UnsupportedResolutionException.java | 42 ++++ .../metadata/serialization/Serializers.java | 20 +- .../interpreter/BuildTimeConstantPool.java | 121 +++++++--- .../svm/interpreter/CremaLinkResolver.java | 90 ++++++++ .../svm/interpreter/CremaRuntimeAccess.java | 211 +++++++++++++++++ .../svm/interpreter/CremaSupportImpl.java | 7 +- .../oracle/svm/interpreter/Interpreter.java | 155 ++++++++----- .../RuntimeInterpreterConstantPool.java | 214 ++++++++++++++++++ 13 files changed, 1031 insertions(+), 122 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/UnsupportedResolutionException.java create mode 100644 substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaLinkResolver.java create mode 100644 substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaRuntimeAccess.java create mode 100644 substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/constantpool/RuntimeInterpreterConstantPool.java diff --git a/espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserConstantPool.java b/espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserConstantPool.java index d9cbd23ed11a..7f9064ae2ba2 100644 --- a/espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserConstantPool.java +++ b/espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ParserConstantPool.java @@ -24,9 +24,12 @@ */ package com.oracle.truffle.espresso.classfile; +import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.function.Function; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.espresso.classfile.descriptors.ModifiedUTF8; import com.oracle.truffle.espresso.classfile.descriptors.Symbol; public final class ParserConstantPool extends ConstantPool { @@ -67,4 +70,69 @@ public ParserConstantPool patchForHiddenClass(int thisKlassIndex, Symbol newN return new ParserConstantPool(newTags, newEntries, newSymbols, majorVersion, minorVersion); } + /** + * Creates a precise dump of the symbolic CP information, unlike {@link #toRawBytes()}, which + * may not be precisely reversible. The {@link ParserConstantPool} can be de-serialized with + * {@link #fromBytesForSerialization(byte[], Symbolify)}. + */ + public byte[] toBytesForSerialization() { + int byteSize = 0; + byteSize += Integer.BYTES; // majorVersion + byteSize += Integer.BYTES; // minorVersion + byteSize += Integer.BYTES; // length + assert tags.length == entries.length; + byteSize += tags.length; // tags + byteSize += entries.length * Integer.BYTES; // entries + byteSize += Integer.BYTES; // symbols.length + for (Symbol symbol : symbols) { + byteSize += Integer.BYTES; // sym.length + byteSize += symbol.length(); // sym.bytes + } + byte[] bytes = new byte[byteSize]; + ByteBuffer bb = ByteBuffer.wrap(bytes); + bb.putInt(majorVersion); + bb.putInt(minorVersion); + bb.putInt(tags.length); + bb.put(tags); + for (int entry : entries) { + bb.putInt(entry); + } + bb.putInt(symbols.length); + for (Symbol symbol : symbols) { + bb.putInt(symbol.length()); + symbol.writeTo(bb); + } + assert !bb.hasRemaining(); + return bytes; + } + + @FunctionalInterface + public interface Symbolify extends Function> { + } + + /** + * Recovers a symbolic CP information, from bytes produces by {@link #toRawBytes()}. + */ + public static ParserConstantPool fromBytesForSerialization(byte[] bytes, Symbolify symbolify) { + ByteBuffer bb = ByteBuffer.wrap(bytes); + int majorVersion = bb.getInt(); + int minorVersion = bb.getInt(); + int length = bb.getInt(); + byte[] tags = new byte[length]; + bb.get(tags); + int[] entries = new int[length]; + for (int i = 0; i < length; i++) { + entries[i] = bb.getInt(); + } + int symbolsLength = bb.getInt(); + Symbol[] symbols = new Symbol[symbolsLength]; + for (int i = 0; i < symbolsLength; i++) { + int symbolLength = bb.getInt(); + byte[] symbolBytes = new byte[symbolLength]; + bb.get(symbolBytes); + symbols[i] = symbolify.apply(symbolBytes); + } + assert !bb.hasRemaining(); + return new ParserConstantPool(tags, entries, symbols, majorVersion, minorVersion); + } } diff --git a/espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/SignatureSymbols.java b/espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/SignatureSymbols.java index ff3cd3bc8f28..6378f2504cc7 100644 --- a/espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/SignatureSymbols.java +++ b/espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/SignatureSymbols.java @@ -105,6 +105,17 @@ public Symbol getOrCreateValidSignature(ByteSequence signatureBytes) return getOrCreateValidSignature(signatureBytes, false); } + /** + * Creates or retrieves a valid method signature symbol from a String. + * + * @return The signature Symbol if valid, null otherwise + * + * @see Validation#validSignatureDescriptor(ByteSequence) + */ + public Symbol getOrCreateValidSignature(String signatureString) { + return getOrCreateValidSignature(ByteSequence.create(signatureString), false); + } + /** * Creates or retrieves a valid method signature symbol from a byte sequence. * diff --git a/espresso-shared/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/RuntimeAccess.java b/espresso-shared/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/RuntimeAccess.java index 210c16150795..d7d23b586c16 100644 --- a/espresso-shared/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/RuntimeAccess.java +++ b/espresso-shared/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/meta/RuntimeAccess.java @@ -32,6 +32,15 @@ * Provides access to some VM-specific capabilities, such as throwing exceptions, or obtaining the * implementor's supported {@link JavaVersion}. * + *

Simple message format

+ *

+ * A strict subset of {@link String#format(String, Object...)} that ONLY supports: + *

    + *
  • "%s" -> {@link Object#toString()}
  • + *
  • "%n" -> {@link System#lineSeparator()}
  • + *
  • "%%" -> "%"
  • The number of arguments and modifiers must match exactly. + *
+ * * @param The class providing access to the VM-side java {@link Class}. * @param The class providing access to the VM-side java {@link java.lang.reflect.Method}. * @param The class providing access to the VM-side java {@link java.lang.reflect.Field}. @@ -47,13 +56,13 @@ public interface RuntimeAccess, M extends MethodAc * is given by the passed {@link ErrorType}. *

* The caller provides an error message that can be constructed using - * {@code String.format(Locale.ENGLISH, messageFormat, args)}. + * Simple message format */ RuntimeException throwError(ErrorType error, String messageFormat, Object... args); /** * If {@code error} is an exception that can be thrown by {@link #throwError}, returns the - * correspondin {@link ErrorType}. Returns null otherwise. + * corresponding {@link ErrorType}. Returns null otherwise. */ ErrorType getErrorType(Throwable error); @@ -81,7 +90,7 @@ public interface RuntimeAccess, M extends MethodAc * aborted. *

* The caller provides an error message that can be constructed using - * {@code String.format(Locale.ENGLISH, messageFormat, args)}. + * Simple message format */ RuntimeException fatal(String messageFormat, Object... args); @@ -89,7 +98,7 @@ public interface RuntimeAccess, M extends MethodAc * Signals that an unexpected exception was seen and that the current operation must be aborted. *

* The caller provides the unexpected exception and an error message that can be constructed - * using {@code String.format(Locale.ENGLISH, messageFormat, args)}. + * using Simple message format */ RuntimeException fatal(Throwable t, String messageFormat, Object... args); } diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterConstantPool.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterConstantPool.java index 47ec6cf8ac43..0d7e7c4b6fd2 100644 --- a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterConstantPool.java +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/InterpreterConstantPool.java @@ -33,23 +33,34 @@ import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.espresso.classfile.ConstantPool; import com.oracle.svm.espresso.classfile.ParserConstantPool; -import com.oracle.svm.espresso.classfile.descriptors.Symbol; import com.oracle.svm.interpreter.metadata.serialization.VisibleForSerialization; -import jdk.vm.ci.meta.ConstantPool; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaField; import jdk.vm.ci.meta.JavaMethod; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.Signature; +import jdk.vm.ci.meta.UnresolvedJavaField; +import jdk.vm.ci.meta.UnresolvedJavaMethod; +import jdk.vm.ci.meta.UnresolvedJavaType; -public final class InterpreterConstantPool extends com.oracle.svm.espresso.classfile.ConstantPool implements ConstantPool { +/** + * JVMCI's {@link jdk.vm.ci.meta.ConstantPool} is not designed to be used in a performance-sensitive + * bytecode interpreter, so a Espresso-like CP implementation is used instead for performance. + *

+ * This class doesn't support runtime resolution on purpose, but supports pre-resolved entries + * instead for AOT types. + */ +public class InterpreterConstantPool extends ConstantPool implements jdk.vm.ci.meta.ConstantPool { + + final InterpreterResolvedObjectType holder; + final ParserConstantPool parserConstantPool; - private final InterpreterResolvedObjectType holder; // Assigned after analysis. - @UnknownObjectField(types = Object[].class) private Object[] entries; + @UnknownObjectField(types = Object[].class) protected Object[] cachedEntries; Object objAt(int cpi) { if (cpi == 0) { @@ -60,23 +71,28 @@ Object objAt(int cpi) { // where an appropriate error should be thrown. throw VMError.shouldNotReachHere("Cannot resolve CP entry 0"); } - return entries[cpi]; + return cachedEntries[cpi]; } - private InterpreterConstantPool(InterpreterResolvedObjectType holder, Object[] entries) { - super(new byte[]{}, new int[]{}, Symbol.EMPTY_ARRAY, 0, 0); + protected InterpreterConstantPool(InterpreterResolvedObjectType holder, ParserConstantPool parserConstantPool, Object[] cachedEntries) { + super(parserConstantPool); this.holder = MetadataUtil.requireNonNull(holder); - this.entries = MetadataUtil.requireNonNull(entries); + this.parserConstantPool = parserConstantPool; + this.cachedEntries = MetadataUtil.requireNonNull(cachedEntries); + } + + protected InterpreterConstantPool(InterpreterResolvedObjectType holder, ParserConstantPool parserConstantPool) { + this(holder, parserConstantPool, new Object[parserConstantPool.length()]); } @VisibleForSerialization - public static InterpreterConstantPool create(InterpreterResolvedObjectType holder, Object[] entries) { - return new InterpreterConstantPool(holder, entries); + public static InterpreterConstantPool create(InterpreterResolvedObjectType holder, ParserConstantPool parserConstantPool, Object[] cachedEntries) { + return new InterpreterConstantPool(holder, parserConstantPool, cachedEntries); } @Override public int length() { - return entries.length; + return cachedEntries.length; } @Override @@ -128,25 +144,24 @@ public JavaConstant lookupAppendix(int cpi, int opcode) { @VisibleForSerialization @Platforms(Platform.HOSTED_ONLY.class) - public Object[] getEntries() { - return entries; + public Object[] getCachedEntries() { + return cachedEntries; + } + + public Object peekCachedEntry(int cpi) { + return cachedEntries[cpi]; } public InterpreterResolvedObjectType getHolder() { return holder; } - // region Unimplemented methods - @Override public RuntimeException classFormatError(String message) { throw new ClassFormatError(message); } - @Override - public ParserConstantPool getParserConstantPool() { - throw VMError.unimplemented("getParserConstantPool"); - } + // region Unimplemented methods @Override public void loadReferencedType(int cpi, int opcode) { @@ -169,4 +184,89 @@ public Signature lookupSignature(int cpi) { } // endregion Unimplemented methods + + @Override + public ParserConstantPool getParserConstantPool() { + return parserConstantPool; + } + + protected Object resolve(int cpi, @SuppressWarnings("unused") InterpreterResolvedObjectType accessingClass) { + assert Thread.holdsLock(this); + assert cpi != 0; // guaranteed by the caller + + @SuppressWarnings("unused") + Tag tag = tagAt(cpi); // CPI bounds check + + Object entry = cachedEntries[cpi]; + if (isUnresolved(entry)) { + /* + * Runtime resolution is deliberately unsupported for AOT types (using base + * InterpreterConstantPool). This can be relaxed in the future e.g. by attaching a + * RuntimeInterpreterConstantPool instead. + */ + throw new UnsupportedResolutionException(); + } + + return entry; + } + + public Object resolvedAt(int cpi, InterpreterResolvedObjectType accessingClass) { + Object entry = cachedEntries[cpi]; + if (isUnresolved(entry)) { + // TODO(peterssen): GR-68611 Avoid deadlocks when hitting breakpoints (JDWP debugger) + // during class resolution. + /* + * Class resolution can run arbitrary code (not in the to-be resolved class + * but) in the user class loaders where it can hit a breakpoint (JDWP debugger), causing + * a deadlock. + */ + synchronized (this) { + entry = cachedEntries[cpi]; + if (isUnresolved(entry)) { + cachedEntries[cpi] = entry = resolve(cpi, accessingClass); + } + } + } + + assert !isUnresolved(entry); + if (entry instanceof Throwable throwable) { + // Cached exception. + throw uncheckedThrow(throwable); + } + + return entry; + } + + private static boolean isUnresolved(Object entry) { + return entry == null || entry instanceof UnresolvedJavaType || entry instanceof UnresolvedJavaMethod || entry instanceof UnresolvedJavaField; + } + + @SuppressWarnings("unchecked") + private static RuntimeException uncheckedThrow(Throwable t) throws T { + throw (T) t; + } + + public InterpreterResolvedJavaField resolvedFieldAt(InterpreterResolvedObjectType accessingKlass, int cpi) { + Object resolvedEntry = resolvedAt(cpi, accessingKlass); + assert resolvedEntry != null; + return (InterpreterResolvedJavaField) resolvedEntry; + } + + public InterpreterResolvedJavaMethod resolvedMethodAt(InterpreterResolvedObjectType accessingKlass, int cpi) { + Object resolvedEntry = resolvedAt(cpi, accessingKlass); + assert resolvedEntry != null; + return (InterpreterResolvedJavaMethod) resolvedEntry; + } + + public InterpreterResolvedObjectType resolvedTypeAt(InterpreterResolvedObjectType accessingKlass, int cpi) { + Object resolvedEntry = resolvedAt(cpi, accessingKlass); + assert resolvedEntry != null; + return (InterpreterResolvedObjectType) resolvedEntry; + } + + public String resolveStringAt(int cpi) { + Object resolvedEntry = resolvedAt(cpi, null); + assert resolvedEntry != null; + return (String) resolvedEntry; + } } 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 31584ef04773..a7283cc47396 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 @@ -32,12 +32,15 @@ import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.registry.SymbolsSupport; import com.oracle.svm.core.util.VMError; import com.oracle.svm.espresso.classfile.ParserKlass; +import com.oracle.svm.espresso.classfile.descriptors.ByteSequence; import com.oracle.svm.espresso.classfile.descriptors.Name; import com.oracle.svm.espresso.classfile.descriptors.Signature; 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.serialization.VisibleForSerialization; import jdk.vm.ci.meta.JavaConstant; @@ -51,6 +54,7 @@ public final class InterpreterResolvedObjectType extends InterpreterResolvedJava private final InterpreterResolvedObjectType superclass; private final InterpreterResolvedObjectType[] interfaces; private InterpreterResolvedJavaMethod[] declaredMethods; + private InterpreterResolvedJavaField[] declaredFields; private int afterFieldsOffset; // Populated after analysis. @@ -272,6 +276,10 @@ public void setDeclaredMethods(InterpreterResolvedJavaMethod[] declaredMethods) this.declaredMethods = declaredMethods; } + public void setDeclaredFields(InterpreterResolvedJavaField[] declaredFields) { + this.declaredFields = declaredFields; + } + public void setAfterFieldsOffset(int afterFieldsOffset) { this.afterFieldsOffset = afterFieldsOffset; } @@ -297,31 +305,70 @@ public InterpreterResolvedJavaType getSuperClass() { @Override public InterpreterResolvedJavaType getHostType() { - throw VMError.unimplemented("getJavaName"); + throw VMError.unimplemented("getHostType"); } @Override public Symbol getSymbolicRuntimePackage() { - throw VMError.unimplemented("getSymbolicRuntimePackage"); + ByteSequence hostPkgName = TypeSymbols.getRuntimePackage(getSymbolicType()); + return SymbolsSupport.getNames().getOrCreate(hostPkgName); } @Override public InterpreterResolvedJavaField lookupField(Symbol name, Symbol type) { - throw VMError.unimplemented("lookupField"); + for (InterpreterResolvedJavaField field : this.declaredFields) { + if (name.equals(field.getSymbolicName()) && type.equals(field.getType().getSymbolicType())) { + return field; + } + } + for (InterpreterResolvedJavaType superInterface : getInterfaces()) { + InterpreterResolvedJavaField result = superInterface.lookupField(name, type); + if (result != null) { + return result; + } + } + if (getSuperclass() != null) { + return getSuperclass().lookupField(name, type); + } + return null; } @Override public InterpreterResolvedJavaMethod lookupMethod(Symbol name, Symbol signature) { - throw VMError.unimplemented("lookupMethod"); + InterpreterResolvedObjectType current = this; + while (current != null) { + for (InterpreterResolvedJavaMethod method : declaredMethods) { + if (name.equals(method.getSymbolicName()) && signature.equals(method.getSymbolicSignature())) { + return method; + } + } + current = current.getSuperclass(); + } + return null; } @Override public InterpreterResolvedJavaMethod lookupInstanceMethod(Symbol name, Symbol signature) { - throw VMError.unimplemented("lookupInstanceMethod"); + InterpreterResolvedObjectType current = this; + while (current != null) { + for (InterpreterResolvedJavaMethod method : declaredMethods) { + if (!method.isStatic() && name.equals(method.getSymbolicName()) && signature.equals(method.getSymbolicSignature())) { + return method; + } + } + current = current.getSuperclass(); + } + return null; } @Override public InterpreterResolvedJavaMethod lookupInterfaceMethod(Symbol name, Symbol signature) { + assert isInterface(); + for (InterpreterResolvedJavaMethod method : declaredMethods) { + if (name.equals(method.getSymbolicName()) && signature.equals(method.getSymbolicSignature())) { + return method; + } + } throw VMError.unimplemented("lookupInterfaceMethod"); } } diff --git a/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/UnsupportedResolutionException.java b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/UnsupportedResolutionException.java new file mode 100644 index 000000000000..7373d41074b5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter.metadata/src/com/oracle/svm/interpreter/metadata/UnsupportedResolutionException.java @@ -0,0 +1,42 @@ +/* + * 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.metadata; + +import java.io.Serial; + +/** + * Thrown when CP resolution is not supported for a specific entry in a constant pool, this is + * usually for constant pools derived, at build time, from JVMCI data structures. + */ +public final class UnsupportedResolutionException extends UnsupportedOperationException { + + @Serial private static final long serialVersionUID = 999753019083783068L; + + @Override + @SuppressWarnings("sync-override") + public Throwable fillInStackTrace() { + return this; + } +} 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 8af19bf757c3..10a2485e0601 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 @@ -36,8 +36,13 @@ import org.graalvm.word.Pointer; import com.oracle.svm.core.FunctionPointerHolder; +import com.oracle.svm.core.hub.registry.SymbolsSupport; import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.espresso.classfile.ParserConstantPool; +import com.oracle.svm.espresso.classfile.descriptors.ByteSequence; +import com.oracle.svm.espresso.classfile.descriptors.ModifiedUTF8; +import com.oracle.svm.espresso.classfile.descriptors.Symbol; import com.oracle.svm.interpreter.metadata.InterpreterConstantPool; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; @@ -523,12 +528,21 @@ public static ValueSerializer> newReferenceConstantSerializ static final ValueSerializer CONSTANT_POOL = createSerializer( (context, in) -> { InterpreterResolvedObjectType holder = context.readReference(in); - Object[] entries = context.readerFor(Object[].class).read(context, in); - return InterpreterConstantPool.create(holder, entries); + byte[] parserConstantPoolBytes = context.readerFor(byte[].class).read(context, in); + + @SuppressWarnings("unchecked") + ParserConstantPool parserConstantPool = ParserConstantPool.fromBytesForSerialization(parserConstantPoolBytes, + bytes -> { + ByteSequence byteSequence = ByteSequence.wrap(bytes); + return (Symbol) SymbolsSupport.getUtf8().getOrCreateValidUtf8(byteSequence); + }); + Object[] cachedEntries = context.readerFor(Object[].class).read(context, in); + return InterpreterConstantPool.create(holder, parserConstantPool, cachedEntries); }, (context, out, value) -> { context.writeReference(out, value.getHolder()); - context.writerFor(Object[].class).write(context, out, value.getEntries()); + context.writerFor(byte[].class).write(context, out, value.getParserConstantPool().toBytesForSerialization()); + context.writerFor(Object[].class).write(context, out, value.getCachedEntries()); }); static final ValueSerializer RESOLVED_FIELD = createSerializer( diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeConstantPool.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeConstantPool.java index bb667e9adba2..d1b16b02ba43 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeConstantPool.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/BuildTimeConstantPool.java @@ -47,25 +47,29 @@ import java.util.List; import java.util.Map; -import com.oracle.graal.pointsto.heap.ImageHeapConstant; -import com.oracle.svm.interpreter.metadata.BytecodeStream; -import com.oracle.svm.interpreter.metadata.Bytecodes; -import com.oracle.svm.interpreter.metadata.InterpreterConstantPool; -import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; -import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; -import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; -import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; -import com.oracle.svm.interpreter.metadata.ReferenceConstant; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; +import com.oracle.graal.pointsto.heap.ImageHeapConstant; import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.espresso.classfile.ConstantPool.Tag; +import com.oracle.svm.espresso.classfile.ParserConstantPool; +import com.oracle.svm.espresso.classfile.descriptors.Symbol; import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedUniverse; +import com.oracle.svm.interpreter.classfile.ClassFile; +import com.oracle.svm.interpreter.metadata.BytecodeStream; +import com.oracle.svm.interpreter.metadata.Bytecodes; +import com.oracle.svm.interpreter.metadata.InterpreterConstantPool; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; +import com.oracle.svm.interpreter.metadata.ReferenceConstant; import jdk.vm.ci.meta.ConstantPool; import jdk.vm.ci.meta.ExceptionHandler; @@ -74,6 +78,7 @@ import jdk.vm.ci.meta.JavaMethod; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.LocalVariableTable; +import jdk.vm.ci.meta.PrimitiveConstant; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.UnresolvedJavaField; import jdk.vm.ci.meta.UnresolvedJavaMethod; @@ -101,18 +106,77 @@ final class BuildTimeConstantPool { private final Map methodCPI; final ArrayList entries; + final ArrayList tags; /** * Creates runtime-ready constant pool for the interpreter. */ public InterpreterConstantPool snapshot() { - return InterpreterConstantPool.create(holder, entries.toArray()); + // Contains a partial parser/symbolic constant pool. + return InterpreterConstantPool.create(holder, buildParserConstantPool(), entries.toArray()); + } + + /** + * Currently, it's not possible to derive a perfect parser/symbolic CP representation from JVMCI + * data-structures, for types serialized at build-time only primitive entries are stored. + */ + private ParserConstantPool buildParserConstantPool() { + int length = entries.size(); + + byte[] byteTags = new byte[length]; + for (int i = 0; i < length; ++i) { + Tag tag = tags.get(i); + if (tag == null) { + tag = Tag.INVALID; + } + byteTags[i] = tag.getValue(); + } + + int[] primitiveEntries = new int[length]; + for (int i = 0; i < length; ++i) { + Tag tag = tags.get(i); + if (tag == null) { + continue; + } + switch (tag) { + case INTEGER -> { + int intValue = ((PrimitiveConstant) entries.get(i)).asInt(); + primitiveEntries[i] = intValue; + } + case FLOAT -> { + float floatValue = ((PrimitiveConstant) entries.get(i)).asFloat(); + primitiveEntries[i] = Float.floatToRawIntBits(floatValue); + } + case DOUBLE -> { + double doubleValue = ((PrimitiveConstant) entries.get(i)).asDouble(); + long doubleBits = Double.doubleToRawLongBits(doubleValue); + primitiveEntries[i] = (int) (doubleBits >> 32); + primitiveEntries[i + 1] = (int) (doubleBits & 0xFFFFFFFFL); + } + case LONG -> { + long longValue = ((PrimitiveConstant) entries.get(i)).asLong(); + primitiveEntries[i] = (int) (longValue >> 32); + primitiveEntries[i + 1] = (int) (longValue & 0xFFFFFFFFL); + } + } + } + + // TODO(peterssen): GR-68564 Obtain proper major/minor version for this type. + return new ParserConstantPool(byteTags, primitiveEntries, Symbol.EMPTY_ARRAY, ClassFile.MAJOR_VERSION, ClassFile.MINOR_VERSION); + } + + private int appendConstant(Tag tag, Object value) { + assert entries.size() == tags.size(); + entries.add(value); + tags.add(tag); + return entries.size() - 1; } BuildTimeConstantPool(InterpreterResolvedObjectType holder) { this.holder = holder; this.entries = new ArrayList<>(32); - this.entries.add(null); // index 0 always contains illegal entry + this.tags = new ArrayList<>(32); + appendConstant(Tag.INVALID, null); // index 0 always contains illegal entry this.constantCPI = new HashMap<>(); this.fieldCPI = new HashMap<>(); this.typeCPI = new HashMap<>(); @@ -127,40 +191,39 @@ public int length() { public int longConstant(long value) { return constantCPI.computeIfAbsent(value, key -> { JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().primitiveConstant(value); - entries.add(javaConstant); - return entries.size() - 1; + int cpi = appendConstant(Tag.LONG, javaConstant); + appendConstant(Tag.INVALID, null); + return cpi; }); } public int intConstant(int value) { return constantCPI.computeIfAbsent(value, key -> { JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().primitiveConstant(value); - entries.add(javaConstant); - return entries.size() - 1; + return appendConstant(Tag.INTEGER, javaConstant); }); } public int floatConstant(float value) { return constantCPI.computeIfAbsent(value, key -> { JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().primitiveConstant(value); - entries.add(javaConstant); - return entries.size() - 1; + return appendConstant(Tag.FLOAT, javaConstant); }); } public int doubleConstant(double value) { return constantCPI.computeIfAbsent(value, key -> { JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().primitiveConstant(value); - entries.add(javaConstant); - return entries.size() - 1; + int cpi = appendConstant(Tag.DOUBLE, javaConstant); + appendConstant(Tag.INVALID, null); + return cpi; }); } public int stringConstant(String value) { return constantCPI.computeIfAbsent(value, key -> { JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().stringConstant(value); - entries.add(javaConstant); - return entries.size() - 1; + return appendConstant(Tag.STRING, javaConstant); }); } @@ -169,8 +232,7 @@ public int typeConstant(JavaType type) { throw new IllegalArgumentException("Type must be either InterpreterResolvedJavaType or UnresolvedJavaType"); } return typeCPI.computeIfAbsent(type, key -> { - entries.add(type); - return entries.size() - 1; + return appendConstant(Tag.CLASS, type); }); } @@ -179,8 +241,7 @@ public int method(JavaMethod method) { throw new IllegalArgumentException("Type must be either InterpreterResolvedJavaMethod or UnresolvedJavaMethod"); } return methodCPI.computeIfAbsent(method, (key) -> { - entries.add(method); - return entries.size() - 1; + return appendConstant(Tag.METHOD_REF, method); }); } @@ -189,24 +250,22 @@ public int field(JavaField field) { throw new IllegalArgumentException("Type must be either InterpreterResolvedJavaField or UnresolvedJavaField"); } return fieldCPI.computeIfAbsent(field, (key) -> { - entries.add(field); - return entries.size() - 1; + return appendConstant(Tag.FIELD_REF, field); }); } private int appendixConstant(JavaConstant appendix) { assert appendix instanceof ReferenceConstant || appendix.isNull(); return appendixCPI.computeIfAbsent(appendix, key -> { - entries.add(appendix); - return entries.size() - 1; + return appendConstant(Tag.INVOKEDYNAMIC, appendix); }); } public int weakObjectConstant(ImageHeapConstant imageHeapConstant) { return constantCPI.computeIfAbsent(imageHeapConstant, key -> { JavaConstant javaConstant = BuildTimeInterpreterUniverse.singleton().weakObjectConstant(imageHeapConstant); - entries.add(javaConstant); - return entries.size() - 1; + // Can't put arbitrary objects on the CP, (ab)used INVOKEDYNAMIC tag as a workaround. + return appendConstant(Tag.INVOKEDYNAMIC, javaConstant); }); } diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaLinkResolver.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaLinkResolver.java new file mode 100644 index 000000000000..8687cd09456b --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaLinkResolver.java @@ -0,0 +1,90 @@ +/* + * 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.espresso.classfile.descriptors.Name; +import com.oracle.svm.espresso.classfile.descriptors.Signature; +import com.oracle.svm.espresso.classfile.descriptors.Symbol; +import com.oracle.svm.espresso.classfile.descriptors.Type; +import com.oracle.svm.espresso.shared.resolver.CallSiteType; +import com.oracle.svm.espresso.shared.resolver.FieldAccessType; +import com.oracle.svm.espresso.shared.resolver.LinkResolver; +import com.oracle.svm.espresso.shared.resolver.ResolvedCall; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; + +public final class CremaLinkResolver { + private CremaLinkResolver() { + // no instance. + } + + public static InterpreterResolvedJavaField resolveFieldSymbolOrThrow(CremaRuntimeAccess runtime, InterpreterResolvedJavaType accessingClass, + Symbol name, Symbol type, InterpreterResolvedJavaType symbolicHolder, + boolean accessCheck, boolean loadingConstraints) { + return LinkResolver.resolveFieldSymbolOrThrow(runtime, accessingClass, name, type, symbolicHolder, accessCheck, loadingConstraints); + } + + public static InterpreterResolvedJavaField resolveFieldSymbolOrNull(CremaRuntimeAccess runtime, InterpreterResolvedJavaType accessingClass, + Symbol name, Symbol type, InterpreterResolvedJavaType symbolicHolder, + boolean accessCheck, boolean loadingConstraints) { + return LinkResolver.resolveFieldSymbolOrNull(runtime, accessingClass, name, type, symbolicHolder, accessCheck, loadingConstraints); + } + + public static void checkFieldAccessOrThrow(CremaRuntimeAccess runtime, InterpreterResolvedJavaField symbolicResolution, FieldAccessType fieldAccessType, InterpreterResolvedJavaType currentClass, + InterpreterResolvedJavaMethod currentMethod) { + LinkResolver.checkFieldAccessOrThrow(runtime, symbolicResolution, fieldAccessType, currentClass, currentMethod); + } + + public static boolean checkFieldAccess(CremaRuntimeAccess runtime, InterpreterResolvedJavaField symbolicResolution, FieldAccessType fieldAccessType, InterpreterResolvedJavaType currentClass, + InterpreterResolvedJavaMethod currentMethod) { + return LinkResolver.checkFieldAccess(runtime, symbolicResolution, fieldAccessType, currentClass, currentMethod); + } + + public static InterpreterResolvedJavaMethod resolveMethodSymbol(CremaRuntimeAccess runtime, InterpreterResolvedJavaType accessingClass, + Symbol name, Symbol signature, InterpreterResolvedJavaType symbolicHolder, + boolean interfaceLookup, + boolean accessCheck, boolean loadingConstraints) { + return LinkResolver.resolveMethodSymbol(runtime, accessingClass, name, signature, symbolicHolder, interfaceLookup, accessCheck, loadingConstraints); + } + + public static InterpreterResolvedJavaMethod resolveMethodSymbolOrNull(CremaRuntimeAccess runtime, InterpreterResolvedJavaType accessingClass, + Symbol name, Symbol signature, InterpreterResolvedJavaType symbolicHolder, + boolean interfaceLookup, + boolean accessCheck, boolean loadingConstraints) { + return LinkResolver.resolveMethodSymbolOrNull(runtime, accessingClass, name, signature, symbolicHolder, interfaceLookup, accessCheck, loadingConstraints); + } + + public static ResolvedCall resolveCallSiteOrThrow( + CremaRuntimeAccess runtime, InterpreterResolvedJavaType currentClass, InterpreterResolvedJavaMethod symbolicResolution, CallSiteType callSiteType, + InterpreterResolvedJavaType symbolicHolder) { + return LinkResolver.resolveCallSiteOrThrow(runtime, currentClass, symbolicResolution, callSiteType, symbolicHolder); + } + + public static ResolvedCall resolveCallSiteOrNull(CremaRuntimeAccess runtime, + InterpreterResolvedJavaType currentClass, InterpreterResolvedJavaMethod symbolicResolution, CallSiteType callSiteType, InterpreterResolvedJavaType symbolicHolder) { + return LinkResolver.resolveCallSiteOrNull(runtime, currentClass, symbolicResolution, callSiteType, symbolicHolder); + } +} diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaRuntimeAccess.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaRuntimeAccess.java new file mode 100644 index 000000000000..2aa4aed2f08f --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaRuntimeAccess.java @@ -0,0 +1,211 @@ +/* + * 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 java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; + +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.registry.ClassRegistries; +import com.oracle.svm.core.hub.registry.SymbolsSupport; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.espresso.classfile.JavaVersion; +import com.oracle.svm.espresso.classfile.descriptors.NameSymbols; +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.espresso.shared.meta.ErrorType; +import com.oracle.svm.espresso.shared.meta.KnownTypes; +import com.oracle.svm.espresso.shared.meta.RuntimeAccess; +import com.oracle.svm.espresso.shared.meta.SymbolPool; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; +import com.oracle.svm.interpreter.metadata.MetadataUtil; + +public final class CremaRuntimeAccess implements RuntimeAccess { + + private static final CremaRuntimeAccess INSTANCE = new CremaRuntimeAccess(); + + private final SymbolPool globalSymbolPool = new SymbolPool() { + @Override + public NameSymbols getNames() { + return SymbolsSupport.getNames(); + } + + @Override + public TypeSymbols getTypes() { + return SymbolsSupport.getTypes(); + } + + @Override + public SignatureSymbols getSignatures() { + return SymbolsSupport.getSignatures(); + } + }; + + private final CremaKnownTypes knownTypes = new CremaKnownTypes(); + + public static CremaRuntimeAccess getInstance() { + return INSTANCE; + } + + @Override + public JavaVersion getJavaVersion() { + return JavaVersion.HOST_VERSION; + } + + @Override + public RuntimeException throwError(ErrorType errorType, String messageFormat, Object... args) { + String message = MetadataUtil.fmt(messageFormat, args); + switch (errorType) { + case IllegalAccessError -> throw new IllegalAccessError(message); + case NoSuchFieldError -> throw new NoSuchFieldError(message); + case NoSuchMethodError -> throw new NoSuchMethodError(message); + case IncompatibleClassChangeError -> throw new IncompatibleClassChangeError(message); + case LinkageError -> throw new LinkageError(message); + } + throw fatal(message); + } + + private static String toClassForName(Symbol type) { + String typeString = type.toString(); + if (TypeSymbols.isArray(type)) { + return typeString.replace('/', '.'); + } + // Primitives cannot be resolved via Class.forName, but provide name for completeness. + if (TypeSymbols.isPrimitive(type)) { + // I -> int + // Z -> boolean + // ... + return TypeSymbols.getJavaKind(type).toJavaClass().getName(); + } + assert typeString.startsWith("L") && typeString.endsWith(";"); + return typeString.substring(1, typeString.length() - 1); // drop L and ; + } + + @SuppressWarnings("unchecked") + public static RuntimeException uncheckedThrow(Throwable t) throws T { + throw (T) t; + } + + @Override + public InterpreterResolvedObjectType lookupOrLoadType(Symbol type, InterpreterResolvedJavaType accessingClass) { + String className = toClassForName(type); + try { + Class result = ClassRegistries.forName(className, accessingClass.getJavaClass().getClassLoader()); + assert !result.isPrimitive(); + return (InterpreterResolvedObjectType) DynamicHub.fromClass(result).getInterpreterType(); + } catch (ClassNotFoundException e) { + throw uncheckedThrow(e); + } + } + + @Override + public KnownTypes getKnownTypes() { + return knownTypes; + } + + @Override + public SymbolPool getSymbolPool() { + return globalSymbolPool; + } + + @Override + public RuntimeException fatal(String messageFormat, Object... args) { + return VMError.shouldNotReachHere(MetadataUtil.fmt(messageFormat, args)); + } + + @Override + public RuntimeException fatal(Throwable t, String messageFormat, Object... args) { + return VMError.shouldNotReachHere(MetadataUtil.fmt(messageFormat, args), t); + } + + @Override + public ErrorType getErrorType(Throwable error) { + // Unwrap exceptions thrown by interpreted code. + Throwable throwable = error; + if (error instanceof SemanticJavaException semanticJavaException) { + throwable = semanticJavaException.getCause(); + } + + Class exceptionClass = throwable.getClass(); + if (exceptionClass == IllegalAccessError.class) { + return ErrorType.IllegalAccessError; + } + if (exceptionClass == NoSuchFieldError.class) { + return ErrorType.NoSuchFieldError; + } + if (exceptionClass == NoSuchMethodError.class) { + return ErrorType.NoSuchMethodError; + } + if (exceptionClass == IncompatibleClassChangeError.class) { + return ErrorType.IncompatibleClassChangeError; + } + if (exceptionClass == LinkageError.class) { + return ErrorType.LinkageError; + } + return null; + } + + private static final class CremaKnownTypes implements KnownTypes { + + static InterpreterResolvedJavaType fromJavaClass(Class clazz) { + return (InterpreterResolvedJavaType) DynamicHub.fromClass(clazz).getInterpreterType(); + } + + @Override + public InterpreterResolvedJavaType java_lang_Object() { + return fromJavaClass(Object.class); + } + + @Override + public InterpreterResolvedJavaType java_lang_Throwable() { + return fromJavaClass(Throwable.class); + } + + @Override + public InterpreterResolvedJavaType java_lang_Class() { + return fromJavaClass(Class.class); + } + + @Override + public InterpreterResolvedJavaType java_lang_String() { + return fromJavaClass(String.class); + } + + @Override + public InterpreterResolvedJavaType java_lang_invoke_MethodType() { + return fromJavaClass(MethodType.class); + } + + @Override + public InterpreterResolvedJavaType java_lang_invoke_MethodHandle() { + return fromJavaClass(MethodHandle.class); + } + } +} 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 6c676fea68b1..6c5c3ec7bb8a 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 @@ -31,6 +31,7 @@ import java.util.Arrays; import java.util.List; +import com.oracle.svm.interpreter.constantpool.RuntimeInterpreterConstantPool; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; import org.graalvm.nativeimage.Platform; @@ -127,12 +128,14 @@ public void fillDynamicHubInfo(DynamicHub hub, CremaDispatchTable dispatchTable, InterpreterResolvedObjectType thisType = InterpreterResolvedObjectType.create(table.partialType.parserKlass, hub.getModifiers(), componentType, superType, interfaces, DynamicHub.toClass(hub), false); - // GR-60109 - // thisType.setConstantPool(...); + ParserKlass parserKlass = table.partialType.parserKlass; + thisType.setConstantPool(new RuntimeInterpreterConstantPool(thisType, parserKlass.getConstantPool())); table.registerClass(thisType); thisType.setDeclaredMethods(table.declaredMethods()); + // TODO(peterssen): GR-60069 Set declared fields. + // thisType.setDeclaredFields(declaredFields.toArray(new InterpreterResolvedJavaField[0])); List completeTable = table.cremaVTable(transitiveSuperInterfaces); thisType.setVtable(completeTable.toArray(new InterpreterResolvedJavaMethod[0])); 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 76a8c45e746a..db4358676f0e 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 @@ -268,6 +268,7 @@ import com.oracle.svm.core.jdk.InternalVMMethod; import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.espresso.classfile.ConstantPool; import com.oracle.svm.interpreter.debug.DebuggerEvents; import com.oracle.svm.interpreter.debug.EventKind; import com.oracle.svm.interpreter.debug.SteppingControl; @@ -283,17 +284,18 @@ import com.oracle.svm.interpreter.metadata.MetadataUtil; import com.oracle.svm.interpreter.metadata.ReferenceConstant; import com.oracle.svm.interpreter.metadata.TableSwitch; +import com.oracle.svm.interpreter.metadata.UnsupportedResolutionException; import jdk.graal.compiler.api.directives.GraalDirectives; -import jdk.vm.ci.meta.ConstantPool; import jdk.vm.ci.meta.ExceptionHandler; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaField; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaMethod; import jdk.vm.ci.meta.JavaType; -import jdk.vm.ci.meta.PrimitiveConstant; import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.UnresolvedJavaField; +import jdk.vm.ci.meta.UnresolvedJavaMethod; import jdk.vm.ci.meta.UnresolvedJavaType; /** @@ -1133,9 +1135,14 @@ private static ExceptionHandler resolveExceptionHandler(InterpreterResolvedJavaM JavaType catchType = null; if (!toCheck.isCatchAll()) { // exception handlers are similar to instanceof bytecodes, so we pass instanceof - catchType = resolveTypeOrUnresolved(method, INSTANCEOF, (char) toCheck.catchTypeCPI()); - if (catchType instanceof UnresolvedJavaType) { - // Exception type is not reachable, skip handler. + catchType = resolveTypeOrNullIfUnresolvable(method, INSTANCEOF, (char) toCheck.catchTypeCPI()); + if (catchType == null) { + /* + * TODO(peterssen): GR-68575 Depending on the constraints, this should + * either panic or just propagate the class resolution error. This happens + * when there's a missing or purely symbolic entry in a pre-resolved + * constant pool. Exception type is not reachable/resolvable, skip handler. + */ continue; } } @@ -1176,30 +1183,36 @@ private static void loadConstant(InterpreterFrame frame, InterpreterResolvedJava VMError.guarantee(opcode != LDC2_W); throw noClassDefFoundError(opcode, null); } - ConstantPool pool = getConstantPool(method); - Object constant = pool.lookupConstant(cpi); - if (constant instanceof PrimitiveConstant primitiveConstant) { - JavaKind kind = primitiveConstant.getJavaKind(); - assert !kind.needsTwoSlots() || opcode == LDC2_W; - assert kind.needsTwoSlots() || (opcode == LDC || opcode == LDC_W); - switch (kind) { - case Int -> putInt(frame, top, primitiveConstant.asInt()); - case Float -> putFloat(frame, top, primitiveConstant.asFloat()); - case Long -> putLong(frame, top, primitiveConstant.asLong()); - case Double -> putDouble(frame, top, primitiveConstant.asDouble()); - default -> throw VMError.shouldNotReachHereAtRuntime(); + InterpreterConstantPool pool = getConstantPool(method); + ConstantPool.Tag tag = pool.tagAt(cpi); + switch (tag) { + case INTEGER -> putInt(frame, top, pool.intAt(cpi)); + case FLOAT -> putFloat(frame, top, pool.floatAt(cpi)); + case LONG -> putLong(frame, top, pool.longAt(cpi)); + case DOUBLE -> putDouble(frame, top, pool.doubleAt(cpi)); + case CLASS -> { + InterpreterResolvedJavaType resolvedType = resolveType(method, opcode, cpi); + putObject(frame, top, resolvedType.getJavaClass()); } - } else if (constant instanceof JavaType) { - InterpreterResolvedJavaType resolvedType = resolveType(method, opcode, cpi); - putObject(frame, top, resolvedType.getJavaClass()); - } else if (constant instanceof ReferenceConstant referenceConstant) { - VMError.guarantee(referenceConstant.isNonNull(), FAILURE_CONSTANT_NOT_PART_OF_IMAGE_HEAP); - Object constantValue = referenceConstant.getReferent(); - putObject(frame, top, constantValue); - } else if (constant.equals(JavaConstant.NULL_POINTER)) { - putObject(frame, top, null); - } else { - throw VMError.unimplemented("LDC* constant pool type"); + case STRING -> { + String string = pool.resolveStringAt(cpi); + putObject(frame, top, string); + } + case INVOKEDYNAMIC -> { + // TODO(peterssen): GR-68576 Storing the pre-resolved appendix in the CP is a + // workaround for the JDWP debugger until proper INVOKEDYNAMIC resolution is + // implemented. + Object appendix = pool.resolvedAt(cpi, null); + if (appendix instanceof ReferenceConstant referenceConstant) { + VMError.guarantee(referenceConstant.isNonNull(), FAILURE_CONSTANT_NOT_PART_OF_IMAGE_HEAP); + Object constantValue = referenceConstant.getReferent(); + putObject(frame, top, constantValue); + } else { + // Raw object. + putObject(frame, top, appendix); + } + } + default -> throw VMError.unimplemented("LDC* constant pool type " + tag); } } @@ -1220,16 +1233,16 @@ private static int invoke(InterpreterFrame callerFrame, InterpreterResolvedJavaM if (opcode == INVOKEDYNAMIC) { int appendixCPI = BytecodeStream.readCPI4(code, curBCI) & 0xFFFF; if (appendixCPI != 0) { - JavaConstant appendixConstant = method.getConstantPool().lookupAppendix(appendixCPI, opcode); + Object appendixEntry = method.getConstantPool().resolvedAt(appendixCPI, method.getDeclaringClass()); Object appendix; - if (JavaConstant.NULL_POINTER.equals(appendixConstant)) { + if (JavaConstant.NULL_POINTER.equals(appendixEntry)) { // The appendix is deliberately null. appendix = null; } else { - if (appendixConstant instanceof ReferenceConstant referenceConstant) { + if (appendixEntry instanceof ReferenceConstant referenceConstant) { appendix = referenceConstant.getReferent(); } else { - throw VMError.shouldNotReachHere("Unexpected INVOKEDYNAMIC appendix constant: " + appendixConstant); + throw VMError.shouldNotReachHere("Unexpected INVOKEDYNAMIC appendix constant: " + appendixEntry); } if (appendix == null) { throw SemanticJavaException.raise(new IncompatibleClassChangeError("INVOKEDYNAMIC appendix was not included in the image heap")); @@ -1261,52 +1274,80 @@ private static int invoke(InterpreterFrame callerFrame, InterpreterResolvedJavaM // region Class/Method/Field resolution - private static JavaType resolveTypeOrUnresolved(InterpreterResolvedJavaMethod method, int opcode, char cpi) { + private static InterpreterResolvedJavaType resolveType(InterpreterResolvedJavaMethod method, int opcode, char cpi) { assert opcode == INSTANCEOF || opcode == CHECKCAST || opcode == NEW || opcode == ANEWARRAY || opcode == MULTIANEWARRAY || opcode == LDC || opcode == LDC_W; if (GraalDirectives.injectBranchProbability(GraalDirectives.SLOWPATH_PROBABILITY, cpi == 0)) { throw noClassDefFoundError(opcode, null); } - return getConstantPool(method).lookupType(cpi, opcode); + try { + return getConstantPool(method).resolvedTypeAt(method.getDeclaringClass(), cpi); + } catch (UnsupportedResolutionException e) { + // CP does not support resolution, try to provide a hint of the non-resolvable entry. + UnresolvedJavaType missingType = null; + if (getConstantPool(method).peekCachedEntry(cpi) instanceof UnresolvedJavaType unresolvedJavaType) { + missingType = unresolvedJavaType; + } + throw noClassDefFoundError(opcode, missingType); + } catch (ClassFormatError e) { + // Out-of-bounds CPI or mis-matching tag. + throw SemanticJavaException.raise(e); + } } - private static InterpreterResolvedJavaType resolveType(InterpreterResolvedJavaMethod method, int opcode, char cpi) { - JavaType javaType = resolveTypeOrUnresolved(method, opcode, cpi); - if (GraalDirectives.injectBranchProbability(GraalDirectives.FASTPATH_PROBABILITY, javaType instanceof InterpreterResolvedJavaType)) { - return (InterpreterResolvedJavaType) javaType; + private static InterpreterResolvedJavaType resolveTypeOrNullIfUnresolvable(InterpreterResolvedJavaMethod method, int opcode, char cpi) { + assert opcode == INSTANCEOF || opcode == CHECKCAST || opcode == NEW || opcode == ANEWARRAY || opcode == MULTIANEWARRAY || opcode == LDC || opcode == LDC_W; + if (GraalDirectives.injectBranchProbability(GraalDirectives.SLOWPATH_PROBABILITY, cpi == 0)) { + return null; // CPI 0 is a marker for unresolvable AND unknown entry + } + try { + return getConstantPool(method).resolvedTypeAt(method.getDeclaringClass(), cpi); + } catch (UnsupportedResolutionException e) { + return null; + } catch (ClassFormatError e) { + // Out-of-bounds CPI or mis-matching tag. + // Unrelated to resolution, just propagate the error. + throw SemanticJavaException.raise(e); } - throw noClassDefFoundError(opcode, javaType); } - private static JavaMethod resolveMethodOrUnresolved(InterpreterResolvedJavaMethod method, int opcode, char cpi) { + private static InterpreterResolvedJavaMethod resolveMethod(InterpreterResolvedJavaMethod method, int opcode, char cpi) { assert Bytecodes.isInvoke(opcode); if (GraalDirectives.injectBranchProbability(GraalDirectives.SLOWPATH_PROBABILITY, cpi == 0)) { throw noSuchMethodError(opcode, null); } - return getConstantPool(method).lookupMethod(cpi, opcode); - } - - static InterpreterResolvedJavaMethod resolveMethod(InterpreterResolvedJavaMethod method, int opcode, char cpi) { - JavaMethod javaMethod = resolveMethodOrUnresolved(method, opcode, cpi); - if (GraalDirectives.injectBranchProbability(GraalDirectives.FASTPATH_PROBABILITY, javaMethod instanceof InterpreterResolvedJavaMethod)) { - return (InterpreterResolvedJavaMethod) javaMethod; + try { + return getConstantPool(method).resolvedMethodAt(method.getDeclaringClass(), cpi); + } catch (UnsupportedResolutionException e) { + // CP does not support resolution, try to provide a hint of the non-resolvable entry. + UnresolvedJavaMethod missingMethod = null; + if (getConstantPool(method).peekCachedEntry(cpi) instanceof UnresolvedJavaMethod unresolvedJavaMethod) { + missingMethod = unresolvedJavaMethod; + } + throw noSuchMethodError(opcode, missingMethod); + } catch (ClassFormatError e) { + // Out-of-bounds CPI or mis-matching tag. + throw SemanticJavaException.raise(e); } - throw noSuchMethodError(opcode, javaMethod); } - private static JavaField resolveFieldOrUnresolved(InterpreterResolvedJavaMethod method, int opcode, char cpi) { + private static InterpreterResolvedJavaField resolveField(InterpreterResolvedJavaMethod method, int opcode, char cpi) { assert opcode == GETFIELD || opcode == GETSTATIC || opcode == PUTFIELD || opcode == PUTSTATIC; if (GraalDirectives.injectBranchProbability(GraalDirectives.SLOWPATH_PROBABILITY, cpi == 0)) { throw noSuchFieldError(opcode, null); } - return getConstantPool(method).lookupField(cpi, method, opcode); - } - - private static InterpreterResolvedJavaField resolveField(InterpreterResolvedJavaMethod method, int opcode, char cpi) { - JavaField javaField = resolveFieldOrUnresolved(method, opcode, cpi); - if (GraalDirectives.injectBranchProbability(GraalDirectives.FASTPATH_PROBABILITY, javaField instanceof InterpreterResolvedJavaField)) { - return (InterpreterResolvedJavaField) javaField; + try { + return getConstantPool(method).resolvedFieldAt(method.getDeclaringClass(), cpi); + } catch (UnsupportedResolutionException e) { + // CP does not support resolution, try to provide a hint of the non-resolvable entry. + UnresolvedJavaField missingField = null; + if (getConstantPool(method).peekCachedEntry(cpi) instanceof UnresolvedJavaField unresolvedJavaField) { + missingField = unresolvedJavaField; + } + throw noSuchFieldError(opcode, missingField); + } catch (ClassFormatError e) { + // Out of bounds CPI or mis-matching tag. + throw SemanticJavaException.raise(e); } - throw noSuchFieldError(opcode, javaField); } // endregion Class/Field/Method resolution diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/constantpool/RuntimeInterpreterConstantPool.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/constantpool/RuntimeInterpreterConstantPool.java new file mode 100644 index 000000000000..a323b299dd8a --- /dev/null +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/constantpool/RuntimeInterpreterConstantPool.java @@ -0,0 +1,214 @@ +/* + * 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.constantpool; + +import com.oracle.svm.core.hub.registry.SymbolsSupport; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.espresso.classfile.ParserConstantPool; +import com.oracle.svm.espresso.classfile.descriptors.Name; +import com.oracle.svm.espresso.classfile.descriptors.Signature; +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.CremaLinkResolver; +import com.oracle.svm.interpreter.CremaRuntimeAccess; +import com.oracle.svm.interpreter.SemanticJavaException; +import com.oracle.svm.interpreter.metadata.InterpreterConstantPool; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaField; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaType; +import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; + +import jdk.vm.ci.meta.UnresolvedJavaField; +import jdk.vm.ci.meta.UnresolvedJavaMethod; +import jdk.vm.ci.meta.UnresolvedJavaType; + +public final class RuntimeInterpreterConstantPool extends InterpreterConstantPool { + + public RuntimeInterpreterConstantPool(InterpreterResolvedObjectType holder, ParserConstantPool parserConstantPool) { + super(holder, parserConstantPool); + } + + @Override + public RuntimeException classFormatError(String message) { + throw SemanticJavaException.raise(new ClassFormatError(message)); + } + + @Override + protected Object resolve(int cpi, InterpreterResolvedObjectType accessingClass) { + Tag tag = tagAt(cpi); + return switch (tag) { + case STRING -> resolveStringConstant(cpi, accessingClass); + case FIELD_REF -> resolveFieldRefConstant(cpi, accessingClass); + case INTERFACE_METHOD_REF -> resolveInterfaceMethodRefConstant(cpi, accessingClass); + case METHOD_REF -> resolveClassMethodRefConstant(cpi, accessingClass); + case CLASS -> resolveClassConstant(cpi, accessingClass); + default -> throw VMError.unimplemented("Unimplemented CP resolution for " + tag); + }; + } + + private String resolveStringConstant(int stringIndex, @SuppressWarnings("unused") InterpreterResolvedObjectType accessingKlass) { + int utf8Index = this.stringUtf8Index(stringIndex); + String string = this.utf8At(utf8Index).toString().intern(); // intern? + return string; + } + + private InterpreterResolvedJavaType resolveClassConstant(int classIndex, InterpreterResolvedJavaType accessingKlass) { + assert accessingKlass != null; + assert tagAt(classIndex) == Tag.CLASS; + + Object entry = this.cachedEntries[classIndex]; + Symbol type = null; + + if (entry == null) { + // CP comes from dynamically loaded .class file. + Symbol className = this.className(classIndex); + type = SymbolsSupport.getTypes().fromClassNameEntry(className); + } else if (entry instanceof UnresolvedJavaType unresolvedJavaType) { + // CP comes from build-time JVMCI type, derive type from UnresolvedJavaType. + type = SymbolsSupport.getTypes().getOrCreateValidType(unresolvedJavaType.getName()); + } else { + throw VMError.shouldNotReachHere("Invalid cached CP entry, expected unresolved type, but got " + entry); + } + + assert type != null; + + try { + InterpreterResolvedObjectType result = CremaRuntimeAccess.getInstance().lookupOrLoadType(type, accessingKlass); + return result; + } catch (LinkageError e) { + // Comment from Hotspot: + // Just throw the exception and don't prevent these classes from being loaded for + // virtual machine errors like StackOverflow and OutOfMemoryError, etc. + // Needs clarification to section 5.4.3 of the JVM spec (see 6308271) + this.cachedEntries[classIndex] = e; + throw e; + } + } + + @SuppressWarnings("try") + private InterpreterResolvedJavaField resolveFieldRefConstant(int fieldIndex, InterpreterResolvedObjectType accessingClass) { + assert accessingClass != null; + assert tagAt(fieldIndex) == Tag.FIELD_REF; + + Object entry = this.cachedEntries[fieldIndex]; + + Symbol fieldName = null; + Symbol fieldType = null; + InterpreterResolvedJavaType holder = null; + + if (entry == null) { + // CP comes from dynamically loaded .class file. + fieldName = this.fieldName(fieldIndex); + fieldType = this.fieldType(fieldIndex); + int memberClassIndex = this.memberClassIndex(fieldIndex); + holder = (InterpreterResolvedJavaType) resolvedAt(memberClassIndex, accessingClass); + } else if (entry instanceof UnresolvedJavaField unresolvedJavaField) { + // CP comes from build-time JVMCI type, derive it from UnresolvedJavaField. + fieldName = SymbolsSupport.getNames().getOrCreate(unresolvedJavaField.getName()); + fieldType = SymbolsSupport.getTypes().getOrCreateValidType(unresolvedJavaField.getType().getName()); + Symbol holderType = SymbolsSupport.getTypes().getOrCreateValidType(unresolvedJavaField.getDeclaringClass().getName()); + assert !TypeSymbols.isPrimitive(holderType) && !TypeSymbols.isArray(holderType); + // Perf. note: The holder is re-resolved every-time (never cached). + holder = CremaRuntimeAccess.getInstance().lookupOrLoadType(holderType, accessingClass); + } else { + throw VMError.shouldNotReachHere("Invalid cached CP entry, expected unresolved field, but got " + entry); + } + + // TODO(peterssen): Enable access checks and loading constraints. + InterpreterResolvedJavaField result = CremaLinkResolver.resolveFieldSymbolOrThrow(CremaRuntimeAccess.getInstance(), accessingClass, fieldName, fieldType, holder, false, false); + return result; + } + + private InterpreterResolvedJavaMethod resolveClassMethodRefConstant(int methodIndex, InterpreterResolvedObjectType accessingClass) { + assert accessingClass != null; + assert tagAt(methodIndex) == Tag.METHOD_REF; + + Object entry = this.cachedEntries[methodIndex]; + + Symbol methodName = null; + Symbol methodSignature = null; + InterpreterResolvedJavaType holder = null; + + if (entry == null) { + // CP comes from dynamically loaded .class file. + methodName = this.methodName(methodIndex); + methodSignature = this.methodSignature(methodIndex); + int memberClassIndex = this.memberClassIndex(methodIndex); + holder = (InterpreterResolvedJavaType) resolvedAt(memberClassIndex, accessingClass); + } else if (entry instanceof UnresolvedJavaMethod unresolvedJavaMethod) { + // CP comes from build-time JVMCI type, derive it from UnresolvedJavaMethod. + methodName = SymbolsSupport.getNames().getOrCreate(unresolvedJavaMethod.getName()); + methodSignature = SymbolsSupport.getSignatures().getOrCreateValidSignature(unresolvedJavaMethod.getSignature().toMethodDescriptor()); + Symbol holderType = SymbolsSupport.getTypes().getOrCreateValidType(unresolvedJavaMethod.getDeclaringClass().getName()); + // Perf. note: The holder is re-resolved every-time (never cached). + holder = CremaRuntimeAccess.getInstance().lookupOrLoadType(holderType, accessingClass); + } else { + throw VMError.shouldNotReachHere("Invalid cached CP entry, expected unresolved method, but got " + entry); + } + + // TODO(peterssen): Enable access checks and loading constraints. + InterpreterResolvedJavaMethod classMethod = CremaLinkResolver.resolveMethodSymbol(CremaRuntimeAccess.getInstance(), accessingClass, methodName, methodSignature, holder, false, false, false); + + // TODO(peterssen): Support MethodHandle invoke intrinsics. + + return classMethod; + } + + private InterpreterResolvedJavaMethod resolveInterfaceMethodRefConstant(int interfaceMethodIndex, InterpreterResolvedObjectType accessingClass) { + assert tagAt(interfaceMethodIndex) == Tag.INTERFACE_METHOD_REF; + + Object entry = this.cachedEntries[interfaceMethodIndex]; + + Symbol methodName = null; + Symbol methodSignature = null; + InterpreterResolvedJavaType holder = null; + + if (entry == null) { + // CP comes from dynamically loaded .class file. + methodName = this.methodName(interfaceMethodIndex); + methodSignature = this.methodSignature(interfaceMethodIndex); + int memberClassIndex = this.memberClassIndex(interfaceMethodIndex); + holder = (InterpreterResolvedJavaType) resolvedAt(memberClassIndex, accessingClass); + } else if (entry instanceof UnresolvedJavaMethod unresolvedJavaMethod) { + // CP comes from build-time JVMCI type, derive it from UnresolvedJavaMethod. + methodName = SymbolsSupport.getNames().getOrCreate(unresolvedJavaMethod.getName()); + methodSignature = SymbolsSupport.getSignatures().getOrCreateValidSignature(unresolvedJavaMethod.getSignature().toMethodDescriptor()); + Symbol holderType = SymbolsSupport.getTypes().getOrCreateValidType(unresolvedJavaMethod.getDeclaringClass().getName()); + // Perf. note: The holder is re-resolved every-time (never cached). + holder = CremaRuntimeAccess.getInstance().lookupOrLoadType(holderType, accessingClass); + } else { + throw VMError.shouldNotReachHere("Invalid cached CP entry, expected unresolved method, but got " + entry); + } + + // TODO(peterssen): Enable access checks and loading constraints. + InterpreterResolvedJavaMethod interfaceMethod = CremaLinkResolver.resolveMethodSymbol(CremaRuntimeAccess.getInstance(), accessingClass, methodName, methodSignature, holder, true, false, + false); + + // TODO(peterssen): Support MethodHandle invoke intrinsics. + + return interfaceMethod; + } +}