8315771: [JVMCI] Resolution of bootstrap methods with int[] static arguments

Reviewed-by: dnsimon, psandoz
This commit is contained in:
Sacha Coppey 2023-09-21 17:00:46 +00:00 committed by Doug Simon
parent 83b01cf3c2
commit 015f6f5d94
5 changed files with 194 additions and 18 deletions

View File

@ -771,9 +771,11 @@ C2V_VMENTRY_NULL(jobjectArray, resolveBootstrapMethod, (JNIEnv* env, jobject, AR
}
// Get the indy entry based on CP index
int indy_index = -1;
for (int i = 0; i < cp->resolved_indy_entries_length(); i++) {
if (cp->resolved_indy_entry_at(i)->constant_pool_index() == index) {
indy_index = i;
if (is_indy) {
for (int i = 0; i < cp->resolved_indy_entries_length(); i++) {
if (cp->resolved_indy_entry_at(i)->constant_pool_index() == index) {
indy_index = i;
}
}
}
// Resolve the bootstrap specifier, its name, type, and static arguments
@ -839,6 +841,11 @@ C2V_VMENTRY_NULL(jobjectArray, resolveBootstrapMethod, (JNIEnv* env, jobject, AR
return JVMCIENV->get_jobjectArray(bsmi);
C2V_END
C2V_VMENTRY_0(jint, bootstrapArgumentIndexAt, (JNIEnv* env, jobject, ARGUMENT_PAIR(cp), jint cpi, jint index))
constantPoolHandle cp(THREAD, UNPACK_PAIR(ConstantPool, cp));
return cp->bootstrap_argument_index_at(cpi, index);
C2V_END
C2V_VMENTRY_0(jint, lookupNameAndTypeRefIndexInPool, (JNIEnv* env, jobject, ARGUMENT_PAIR(cp), jint index, jint opcode))
constantPoolHandle cp(THREAD, UNPACK_PAIR(ConstantPool, cp));
return cp->name_and_type_ref_index_at(index, (Bytecodes::Code)opcode);
@ -3177,6 +3184,7 @@ JNINativeMethod CompilerToVM::methods[] = {
{CC "lookupConstantInPool", CC "(" HS_CONSTANT_POOL2 "IZ)" JAVACONSTANT, FN_PTR(lookupConstantInPool)},
{CC "constantPoolRemapInstructionOperandFromCache", CC "(" HS_CONSTANT_POOL2 "I)I", FN_PTR(constantPoolRemapInstructionOperandFromCache)},
{CC "resolveBootstrapMethod", CC "(" HS_CONSTANT_POOL2 "I)[" OBJECT, FN_PTR(resolveBootstrapMethod)},
{CC "bootstrapArgumentIndexAt", CC "(" HS_CONSTANT_POOL2 "II)I", FN_PTR(bootstrapArgumentIndexAt)},
{CC "getUncachedStringInPool", CC "(" HS_CONSTANT_POOL2 "I)" JAVACONSTANT, FN_PTR(getUncachedStringInPool)},
{CC "resolveTypeInPool", CC "(" HS_CONSTANT_POOL2 "I)" HS_KLASS, FN_PTR(resolveTypeInPool)},
{CC "resolveFieldInPool", CC "(" HS_CONSTANT_POOL2 "I" HS_METHOD2 "B[I)" HS_KLASS, FN_PTR(resolveFieldInPool)},

View File

@ -483,6 +483,28 @@ final class CompilerToVM {
private native Object[] resolveBootstrapMethod(HotSpotConstantPool constantPool, long constantPoolPointer, int cpi);
/**
* Gets the constant pool index of a static argument of a {@code CONSTANT_Dynamic_info} or
* @{code CONSTANT_InvokeDynamic_info} entry. Used when the list of static arguments in the
* {@link BootstrapMethodInvocation} is a {@code List<PrimitiveConstant>} of the form
* {{@code arg_count}, {@code pool_index}}, meaning the arguments are not already resolved and that
* the JDK has to lookup the arguments when they are needed. The {@code cpi} corresponds to
* {@code pool_index} and the {@code index} has to be smaller than {@code arg_count}.
*
* The behavior of this method is undefined if {@code cpi} does not denote an entry representing
* a {@code CONSTANT_Dynamic_info} or a @{code CONSTANT_InvokeDynamic_info}, or if the index
* is out of bounds.
*
* @param cpi the index of a {@code CONSTANT_Dynamic_info} or @{code CONSTANT_InvokeDynamic_info} entry
* @param index the index of the static argument in the list of static arguments
* @return the constant pool index associated with the static argument
*/
int bootstrapArgumentIndexAt(HotSpotConstantPool constantPool, int cpi, int index) {
return bootstrapArgumentIndexAt(constantPool, constantPool.getConstantPoolPointer(), cpi, index);
}
private native int bootstrapArgumentIndexAt(HotSpotConstantPool constantPool, long constantPoolPointer, int cpi, int index);
/**
* If {@code cpi} denotes an entry representing a signature polymorphic method ({@jvms 2.9}),
* this method ensures that the type referenced by the entry is loaded and initialized. It

View File

@ -27,6 +27,8 @@ import static jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.runtime;
import static jdk.vm.ci.hotspot.HotSpotVMConfig.config;
import static jdk.vm.ci.hotspot.UnsafeAccess.UNSAFE;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@ -38,6 +40,7 @@ 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.PrimitiveConstant;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
import jdk.vm.ci.meta.Signature;
@ -504,6 +507,60 @@ public final class HotSpotConstantPool implements ConstantPool, MetaspaceHandleO
return UNSAFE.getInt(getConstantPoolPointer() + config().constantPoolFlagsOffset);
}
/**
* Represents a list of static arguments from a {@link BootstrapMethodInvocation} of the form
* {{@code arg_count}, {@code pool_index}}, meaning the arguments are not already resolved
* and that the JDK has to lookup the arguments when they are needed. The {@code bssIndex}
* corresponds to {@code pool_index} and the {@code size} corresponds to {@code arg_count}.
*/
static class CachedBSMArgs extends AbstractList<JavaConstant> {
private final JavaConstant[] cache;
private final HotSpotConstantPool cp;
private final int bssIndex;
CachedBSMArgs(HotSpotConstantPool cp, int bssIndex, int size) {
this.cp = cp;
this.bssIndex = bssIndex;
this.cache = new JavaConstant[size];
}
/**
* Lazily resolves and caches the argument at the given index and returns it. The method
* {@link CompilerToVM#bootstrapArgumentIndexAt} is used to obtain the constant pool
* index of the entry and the method {@link ConstantPool#lookupConstant} is used to
* resolve it. If the resolution failed, the index is returned as a
* {@link PrimitiveConstant}.
*
* @param index index of the element to return
* @return A {@link JavaConstant} corresponding to the static argument requested. A return
* value of type {@link PrimitiveConstant} represents an unresolved constant pool entry
*/
@Override
public JavaConstant get(int index) {
JavaConstant res = cache[index];
if (res == null) {
int argCpi = compilerToVM().bootstrapArgumentIndexAt(cp, bssIndex, index);
Object object = cp.lookupConstant(argCpi, false);
if (object instanceof PrimitiveConstant primitiveConstant) {
res = runtime().getReflection().boxPrimitive(primitiveConstant);
} else if (object instanceof JavaConstant javaConstant) {
res = javaConstant;
} else if (object instanceof JavaType type) {
res = runtime().getReflection().forObject(type);
} else {
res = JavaConstant.forInt(argCpi);
}
cache[index] = res;
}
return res;
}
@Override
public int size() {
return cache.length;
}
}
static class BootstrapMethodInvocationImpl implements BootstrapMethodInvocation {
private final boolean indy;
private final ResolvedJavaMethod method;
@ -582,8 +639,9 @@ public final class HotSpotConstantPool implements ConstantPool, MetaspaceHandleO
staticArgumentsList = List.of((JavaConstant[]) staticArguments);
} else {
int[] bsciArgs = (int[]) staticArguments;
String message = String.format("Resolving bootstrap static arguments for %s using BootstrapCallInfo %s not supported", method.format("%H.%n(%p)"), Arrays.toString(bsciArgs));
throw new IllegalArgumentException(message);
int argCount = bsciArgs[0];
int bss_index = bsciArgs[1];
staticArgumentsList = new CachedBSMArgs(this, bss_index, argCount);
}
return new BootstrapMethodInvocationImpl(tag.name.equals("InvokeDynamic"), method, name, type, staticArgumentsList);
default:

View File

@ -131,7 +131,7 @@ public interface ConstantPool {
/**
* The details for invoking a bootstrap method associated with a {@code CONSTANT_Dynamic_info}
* or {@code CONSTANT_InvokeDynamic_info} pool entry .
* or {@code CONSTANT_InvokeDynamic_info} pool entry.
*
* @jvms 4.4.10 The {@code CONSTANT_Dynamic_info} and {@code CONSTANT_InvokeDynamic_info}
* Structures
@ -165,6 +165,29 @@ public interface ConstantPool {
/**
* Gets the static arguments with which the bootstrap method will be invoked.
*
* The {@linkplain JavaConstant#getJavaKind kind} of each argument will be
* {@link JavaKind#Object} or {@link JavaKind#Int}. The latter represents an
* unresolved {@code CONSTANT_Dynamic_info} entry. To resolve this entry, the
* corresponding bootstrap method has to be called first:
*
* <pre>
* List<JavaConstant> args = bmi.getStaticArguments();
* List<JavaConstant> resolvedArgs = new ArrayList<>(args.size());
* for (JavaConstant c : args) {
* JavaConstant r = c;
* if (c.getJavaKind() == JavaKind.Int) {
* // If needed, access corresponding BootstrapMethodInvocation using
* // cp.lookupBootstrapMethodInvocation(pc.asInt(), -1)
* r = cp.lookupConstant(c.asInt(), true);
* } else {
* assert c.getJavaKind() == JavaKind.Object;
* }
* resolvedArgs.append(r);
* }
* </pre>
*
* The other types of entries are already resolved an can be used directly.
*
* @jvms 5.4.3.6
*/
List<JavaConstant> getStaticArguments();
@ -182,8 +205,6 @@ public interface ConstantPool {
* {@code index} was not decoded from a bytecode stream
* @return the bootstrap method invocation details or {@code null} if the entry specified by {@code index}
* is not a {@code CONSTANT_Dynamic_info} or @{code CONSTANT_InvokeDynamic_info}
* @throws IllegalArgumentException if the bootstrap method invocation makes use of
* {@code java.lang.invoke.BootstrapCallInfo}
* @jvms 4.7.23 The {@code BootstrapMethods} Attribute
*/
default BootstrapMethodInvocation lookupBootstrapMethodInvocation(int index, int opcode) {

View File

@ -32,6 +32,9 @@
* @run testng/othervm
* -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:-UseJVMCICompiler
* jdk.vm.ci.hotspot.test.TestDynamicConstant
* @run testng/othervm
* -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:-UseJVMCICompiler -XX:UseBootstrapCallInfo=3
* jdk.vm.ci.hotspot.test.TestDynamicConstant
*/
package jdk.vm.ci.hotspot.test;
@ -61,6 +64,7 @@ import jdk.vm.ci.hotspot.HotSpotObjectConstant;
import jdk.vm.ci.hotspot.HotSpotConstantPool;
import jdk.vm.ci.meta.ConstantPool;
import jdk.vm.ci.meta.ConstantPool.BootstrapMethodInvocation;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.PrimitiveConstant;
import jdk.vm.ci.meta.ResolvedJavaMethod;
@ -86,6 +90,12 @@ public class TestDynamicConstant implements Opcodes {
*/
CALL_DIRECT_BSM,
/**
* Condy whose bootstrap method is one of the {@code TestDynamicConstant.get<type>BSM(<type> constant, int i)}
* methods with one condy arg and one int arg.
*/
CALL_DIRECT_WITH_ARGS_BSM,
/**
* Condy whose bootstrap method is {@link ConstantBootstraps#invoke} that invokes one of the
* {@code TestDynamicConstant.get<type>()} methods.
@ -164,6 +174,24 @@ public class TestDynamicConstant implements Opcodes {
run.visitInsn(type.getOpcode(IRETURN));
run.visitMaxs(0, 0);
run.visitEnd();
} else if (condyType == CondyType.CALL_DIRECT_WITH_ARGS_BSM) {
// Example: int TestDynamicConstant.getIntBSM(MethodHandles.Lookup l, String name,
// Class<?> type, int constant, int i)
String sig1 = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)" + desc;
String sig2 = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;" + desc + "I)" + desc;
Handle handle1 = new Handle(H_INVOKESTATIC, testClassInternalName, getter + "BSM", sig1, false);
Handle handle2 = new Handle(H_INVOKESTATIC, testClassInternalName, getter + "BSM", sig2, false);
ConstantDynamic condy1 = new ConstantDynamic("const1", desc, handle1);
ConstantDynamic condy2 = new ConstantDynamic("const2", desc, handle2, condy1, Integer.MAX_VALUE);
condy = condy2;
MethodVisitor run = cw.visitMethod(PUBLIC_STATIC, "run", "()" + desc, null, null);
run.visitLdcInsn(condy);
run.visitInsn(type.getOpcode(IRETURN));
run.visitMaxs(0, 0);
run.visitEnd();
} else if (condyType == CondyType.CALL_INDIRECT_BSM) {
// Example: int TestDynamicConstant.getInt()
Handle handle = new Handle(H_INVOKESTATIC, testClassInternalName, getter, "()" + desc, false);
@ -309,6 +337,12 @@ public class TestDynamicConstant implements Opcodes {
assertNoEagerConstantResolution(testClass, cp, getTagAt);
assertLookupBMIDoesNotInvokeBM(metaAccess, testClass);
if (type != Object.class) {
testLookupBootstrapMethodInvocation(condyType, metaAccess, testClass, getTagAt);
} else {
// StringConcatFactoryStringConcatFactory cannot accept null constants
}
Object lastConstant = null;
for (int cpi = 1; cpi < cp.length(); cpi++) {
String tag = String.valueOf(getTagAt.invoke(cp, cpi));
@ -330,12 +364,6 @@ public class TestDynamicConstant implements Opcodes {
actual = ((HotSpotObjectConstant) lastConstant).asObject(type);
}
Assert.assertEquals(actual, expect, m + ":");
if (type != Object.class) {
testLookupBootstrapMethodInvocation(condyType, metaAccess, testClass, getTagAt);
} else {
// StringConcatFactoryStringConcatFactory cannot accept null constants
}
}
}
}
@ -364,10 +392,29 @@ public class TestDynamicConstant implements Opcodes {
Assert.assertTrue(expectedBSMs.contains(bsm), expectedBSMs.toString());
} else {
Assert.assertFalse(bsmi.isInvokeDynamic());
if (condyType == CondyType.CALL_DIRECT_BSM) {
Assert.assertTrue(bsm.startsWith("jdk.vm.ci.hotspot.test.TestDynamicConstant.get") && bsm.endsWith("BSM"), bsm);
} else {
Assert.assertEquals(bsm, "java.lang.invoke.ConstantBootstraps.invoke");
checkBsmName(condyType, bsm);
List<JavaConstant> staticArguments = bsmi.getStaticArguments();
for (int i = 0; i < staticArguments.size(); ++i) {
JavaConstant constant = staticArguments.get(i);
if (constant instanceof PrimitiveConstant) {
String innerTag = String.valueOf(getTagAt.invoke(cp, constant.asInt()));
if (condyType == CondyType.CALL_DIRECT_WITH_ARGS_BSM) {
Assert.assertEquals(i, 0);
Assert.assertEquals(innerTag, "Dynamic");
}
if (innerTag.equals("Dynamic")) {
BootstrapMethodInvocation innerBsmi = cp.lookupBootstrapMethodInvocation(constant.asInt(), -1);
String innerBsm = innerBsmi.getMethod().format("%H.%n");
checkBsmName(condyType, innerBsm);
} else {
Assert.assertEquals(innerTag, "MethodHandle");
}
} else {
if (condyType == CondyType.CALL_DIRECT_WITH_ARGS_BSM) {
Assert.assertEquals(i, 1);
}
Assert.assertTrue(staticArguments.get(i) instanceof HotSpotObjectConstant);
}
}
}
} else {
@ -378,6 +425,14 @@ public class TestDynamicConstant implements Opcodes {
testLoadReferencedType(concat, cp);
}
private static void checkBsmName(CondyType condyType, String bsm) {
if (condyType == CondyType.CALL_DIRECT_BSM || condyType == CondyType.CALL_DIRECT_WITH_ARGS_BSM) {
Assert.assertTrue(bsm.startsWith("jdk.vm.ci.hotspot.test.TestDynamicConstant.get") && bsm.endsWith("BSM"), bsm);
} else {
Assert.assertEquals(bsm, "java.lang.invoke.ConstantBootstraps.invoke");
}
}
private static int beS4(byte[] data, int bci) {
return (data[bci] << 24) | ((data[bci + 1] & 0xff) << 16) | ((data[bci + 2] & 0xff) << 8) | (data[bci + 3] & 0xff);
}
@ -425,6 +480,18 @@ public class TestDynamicConstant implements Opcodes {
@SuppressWarnings("unused") public static Object getObjectBSM (MethodHandles.Lookup l, String name, Class<?> type) { return null; }
@SuppressWarnings("unused") public static List<?> getListBSM (MethodHandles.Lookup l, String name, Class<?> type) { return List.of("element"); }
@SuppressWarnings("unused") public static boolean getBooleanBSM(MethodHandles.Lookup l, String name, Class<?> type, boolean constant, int i) { return true; }
@SuppressWarnings("unused") public static char getCharBSM (MethodHandles.Lookup l, String name, Class<?> type, char constant, int i) { return '*'; }
@SuppressWarnings("unused") public static short getShortBSM (MethodHandles.Lookup l, String name, Class<?> type, short constant, int i) { return Short.MAX_VALUE; }
@SuppressWarnings("unused") public static byte getByteBSM (MethodHandles.Lookup l, String name, Class<?> type, byte constant, int i) { return Byte.MAX_VALUE; }
@SuppressWarnings("unused") public static int getIntBSM (MethodHandles.Lookup l, String name, Class<?> type, int constant, int i) { return Integer.MAX_VALUE; }
@SuppressWarnings("unused") public static float getFloatBSM (MethodHandles.Lookup l, String name, Class<?> type, float constant, int i) { return Float.MAX_VALUE; }
@SuppressWarnings("unused") public static long getLongBSM (MethodHandles.Lookup l, String name, Class<?> type, long constant, int i) { return Long.MAX_VALUE; }
@SuppressWarnings("unused") public static double getDoubleBSM (MethodHandles.Lookup l, String name, Class<?> type, double constant, int i) { return Double.MAX_VALUE; }
@SuppressWarnings("unused") public static String getStringBSM (MethodHandles.Lookup l, String name, Class<?> type, String constant, int i) { return "a string"; }
@SuppressWarnings("unused") public static Object getObjectBSM (MethodHandles.Lookup l, String name, Class<?> type, Object constant, int i) { return null; }
@SuppressWarnings("unused") public static List<?> getListBSM (MethodHandles.Lookup l, String name, Class<?> type, List<?> constant, int i) { return List.of("element"); }
public static boolean getBoolean() { return true; }
public static char getChar () { return '*'; }