Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<T extends ModifiedUTF8> extends Function<byte[], Symbol<T>> {
}

/**
* Recovers a symbolic CP information, from bytes produces by {@link #toRawBytes()}.
*/
public static ParserConstantPool fromBytesForSerialization(byte[] bytes, Symbolify<? extends ModifiedUTF8> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ public Symbol<Signature> 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<Signature> getOrCreateValidSignature(String signatureString) {
return getOrCreateValidSignature(ByteSequence.create(signatureString), false);
}

/**
* Creates or retrieves a valid method signature symbol from a byte sequence.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@
* Provides access to some VM-specific capabilities, such as throwing exceptions, or obtaining the
* implementor's supported {@link JavaVersion}.
*
* <h2 id="simpleFormat">Simple message format</h2>
* <p>
* A strict subset of {@link String#format(String, Object...)} that <b>ONLY</b> supports:
* <ul>
* <li>"%s" -> {@link Object#toString()}</li>
* <li>"%n" -> {@link System#lineSeparator()}</li>
* <li>"%%" -> "%"</li> The number of arguments and modifiers must match exactly.
* </ul>
*
* @param <C> The class providing access to the VM-side java {@link Class}.
* @param <M> The class providing access to the VM-side java {@link java.lang.reflect.Method}.
* @param <F> The class providing access to the VM-side java {@link java.lang.reflect.Field}.
Expand All @@ -47,13 +56,13 @@ public interface RuntimeAccess<C extends TypeAccess<C, M, F>, M extends MethodAc
* is given by the passed {@link ErrorType}.
* <p>
* The caller provides an error message that can be constructed using
* {@code String.format(Locale.ENGLISH, messageFormat, args)}.
* <a href="#simpleFormat">Simple message format</a>
*/
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);

Expand Down Expand Up @@ -81,15 +90,15 @@ public interface RuntimeAccess<C extends TypeAccess<C, M, F>, M extends MethodAc
* aborted.
* <p>
* The caller provides an error message that can be constructed using
* {@code String.format(Locale.ENGLISH, messageFormat, args)}.
* <a href="#simpleFormat">Simple message format</a>
*/
RuntimeException fatal(String messageFormat, Object... args);

/**
* Signals that an unexpected exception was seen and that the current operation must be aborted.
* <p>
* The caller provides the unexpected exception and an error message that can be constructed
* using {@code String.format(Locale.ENGLISH, messageFormat, args)}.
* using <a href="#simpleFormat">Simple message format</a>
*/
RuntimeException fatal(Throwable t, String messageFormat, Object... args);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* 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) {
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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 <clinit>
* 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 <T extends Throwable> 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;
}
}
Loading
Loading