diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index 5affc354ad3..b5325611835 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -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)}, diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java index 98eac1eb5bf..a70624e0aad 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java @@ -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} 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 diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotConstantPool.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotConstantPool.java index 51917d3fb7f..98eb0093c09 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotConstantPool.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotConstantPool.java @@ -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 { + 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: diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java index 5a53de47f6a..2273b256f03 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java @@ -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: + * + *
+         * List args = bmi.getStaticArguments();
+         * List 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);
+         * }
+         * 
+ * + * The other types of entries are already resolved an can be used directly. + * * @jvms 5.4.3.6 */ List 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) { diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.hotspot.test/src/jdk/vm/ci/hotspot/test/TestDynamicConstant.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.hotspot.test/src/jdk/vm/ci/hotspot/test/TestDynamicConstant.java index c69c4e5a345..32135e46bc1 100644 --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.hotspot.test/src/jdk/vm/ci/hotspot/test/TestDynamicConstant.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.hotspot.test/src/jdk/vm/ci/hotspot/test/TestDynamicConstant.java @@ -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.getBSM( 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()} 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 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 '*'; }