< prev index next >
src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
Print this page
@@ -24,10 +24,11 @@
*/
package java.lang.invoke;
import jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.FieldVisitor;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import sun.invoke.util.VerifyAccess;
@@ -40,17 +41,20 @@
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
import java.util.stream.Stream;
import static java.lang.invoke.LambdaForm.BasicType;
import static java.lang.invoke.LambdaForm.BasicType.*;
import static java.lang.invoke.LambdaForm.*;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static java.lang.invoke.MethodHandleStatics.*;
+import static java.lang.invoke.MethodHandles.Lookup.*;
/**
* Code generation backend for LambdaForm.
* <p>
* @author John Rose, JSR 292 EG
@@ -65,10 +69,12 @@
private static final String OBJ = "java/lang/Object";
private static final String OBJARY = "[Ljava/lang/Object;";
private static final String LOOP_CLAUSES = MHI + "$LoopClauses";
private static final String MHARY2 = "[[L" + MH + ";";
+ private static final String MH_SIG = "L" + MH + ";";
+
private static final String LF_SIG = "L" + LF + ";";
private static final String LFN_SIG = "L" + LFN + ";";
private static final String LL_SIG = "(L" + OBJ + ";)L" + OBJ + ";";
private static final String LLV_SIG = "(L" + OBJ + ";L" + OBJ + ";)V";
@@ -90,17 +96,27 @@
private Class<?>[] localClasses; // type
/** ASM bytecode generation. */
private ClassWriter cw;
private MethodVisitor mv;
+ private final List<ClassData> classData = new ArrayList<>();
/** Single element internal class name lookup cache. */
private Class<?> lastClass;
private String lastInternalName;
private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory();
private static final Class<?> HOST_CLASS = LambdaForm.class;
+ private static final MethodHandles.Lookup LOOKUP = lookup();
+
+ private static MethodHandles.Lookup lookup() {
+ try {
+ return MethodHandles.privateLookupIn(HOST_CLASS, IMPL_LOOKUP);
+ } catch (IllegalAccessException e) {
+ throw newInternalError(e);
+ }
+ }
/** Main constructor; other constructors delegate to this one. */
private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize,
String className, String invokerName, MethodType invokerType) {
int p = invokerName.indexOf('.');
@@ -219,45 +235,56 @@
sfx = "0"+sfx;
className += sfx;
return className;
}
- class CpPatch {
- final int index;
+ public static class ClassData {
+ final String name;
+ final String desc;
final Object value;
- CpPatch(int index, Object value) {
- this.index = index;
+
+ ClassData(String name, String desc, Object value) {
+ this.name = name;
+ this.desc = desc;
this.value = value;
}
+
+ public String name() { return name; }
public String toString() {
- return "CpPatch/index="+index+",value="+value;
+ return name + ",value="+value;
}
}
- private final ArrayList<CpPatch> cpPatches = new ArrayList<>();
-
- private int cph = 0; // for counting constant placeholders
+ String classData(Object arg) {
+ String desc;
+ if (arg instanceof Class) {
+ desc = "Ljava/lang/Class;";
+ } else if (arg instanceof MethodHandle) {
+ desc = MH_SIG;
+ } else if (arg instanceof LambdaForm) {
+ desc = LF_SIG;
+ } else {
+ desc = "Ljava/lang/Object;";
+ }
- String constantPlaceholder(Object arg) {
- String cpPlaceholder = "CONSTANT_PLACEHOLDER_" + cph++;
- if (DUMP_CLASS_FILES) cpPlaceholder += " <<" + debugString(arg) + ">>";
- // TODO check if arg is already in the constant pool
- // insert placeholder in CP and remember the patch
- int index = cw.newConst((Object) cpPlaceholder);
- cpPatches.add(new CpPatch(index, arg));
- return cpPlaceholder;
+ Class<?> c = arg.getClass();
+ while (c.isArray()) {
+ c = c.getComponentType();
+ }
+ // unique static variable name
+ String name = "_DATA_" + c.getSimpleName() + "_" + classData.size();
+ ClassData cd = new ClassData(name, desc, arg);
+ classData.add(cd);
+ return cd.name();
}
- Object[] cpPatches(byte[] classFile) {
- int size = getConstantPoolSize(classFile);
- Object[] res = new Object[size];
- for (CpPatch p : cpPatches) {
- if (p.index >= size)
- throw new InternalError("in cpool["+size+"]: "+p+"\n"+Arrays.toString(Arrays.copyOf(classFile, 20)));
- res[p.index] = p.value;
+ List<Object> classDataValues() {
+ Object[] data = new Object[classData.size()];
+ for (int i = 0; i < classData.size(); i++) {
+ data[i] = classData.get(i).value;
}
- return res;
+ return List.of(data);
}
private static String debugString(Object arg) {
if (arg instanceof MethodHandle) {
MethodHandle mh = (MethodHandle) arg;
@@ -286,23 +313,15 @@
/**
* Extract the MemberName of a newly-defined method.
*/
private MemberName loadMethod(byte[] classFile) {
- Class<?> invokerClass = loadAndInitializeInvokerClass(classFile, cpPatches(classFile));
+ Class<?> invokerClass = LOOKUP.makeHiddenClassDefiner(classFile, Set.of(ClassOption.WEAK))
+ .defineClass(true, classDataValues());
return resolveInvokerMember(invokerClass, invokerName, invokerType);
}
- /**
- * Define a given class as anonymous class in the runtime system.
- */
- private static Class<?> loadAndInitializeInvokerClass(byte[] classBytes, Object[] patches) {
- Class<?> invokerClass = UNSAFE.defineAnonymousClass(HOST_CLASS, classBytes, patches);
- UNSAFE.ensureClassInitialized(invokerClass); // Make sure the class is initialized; VM might complain.
- return invokerClass;
- }
-
private static MemberName resolveInvokerMember(Class<?> invokerClass, String name, MethodType type) {
MemberName member = new MemberName(invokerClass, name, type, REF_invokeStatic);
try {
member = MEMBERNAME_FACTORY.resolveOrFail(REF_invokeStatic, member, HOST_CLASS, ReflectiveOperationException.class);
} catch (ReflectiveOperationException e) {
@@ -314,11 +333,12 @@
/**
* Set up class file generation.
*/
private ClassWriter classFilePrologue() {
final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC
- cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
+ setClassWriter(cw);
cw.visit(Opcodes.V1_8, NOT_ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER,
CLASS_PREFIX + className, null, INVOKER_SUPER_NAME, null);
cw.visitSource(SOURCE_PREFIX + className, null);
return cw;
}
@@ -334,10 +354,52 @@
private void methodEpilogue() {
mv.visitMaxs(0, 0);
mv.visitEnd();
}
+ private String className() {
+ return CLASS_PREFIX + className;
+ }
+
+ private void clinit() {
+ clinit(cw, className(), classData);
+ }
+
+ static void clinit(ClassWriter cw, String className, List<ClassData> classData) {
+ if (classData.isEmpty())
+ return;
+
+ for (ClassData p : classData) {
+ // add the static field
+ FieldVisitor fv = cw.visitField(Opcodes.ACC_STATIC|Opcodes.ACC_FINAL, p.name, p.desc, null, null);
+ fv.visitEnd();
+ }
+
+ MethodVisitor mv = cw.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
+ mv.visitCode();
+ // bootstrapping issue if using condy
+ mv.visitLdcInsn(Type.getType("L" + className + ";"));
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandleNatives",
+ "classData", "(Ljava/lang/Class;)Ljava/lang/Object;", false);
+ // we should optimize one single element case that does not need to create a List
+ mv.visitTypeInsn(Opcodes.CHECKCAST, "java/util/List");
+ mv.visitVarInsn(Opcodes.ASTORE, 0);
+ int index = 0;
+ for (ClassData p : classData) {
+ // initialize the static field
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ emitIconstInsn(mv, index++);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List",
+ "get", "(I)Ljava/lang/Object;", true);
+ mv.visitTypeInsn(Opcodes.CHECKCAST, p.desc.substring(1, p.desc.length()-1));
+ mv.visitFieldInsn(Opcodes.PUTSTATIC, className, p.name, p.desc);
+ }
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+
/*
* Low-level emit helpers.
*/
private void emitConst(Object con) {
if (con == null) {
@@ -406,10 +468,14 @@
// fall through:
mv.visitLdcInsn(con);
}
private void emitIconstInsn(final int cst) {
+ emitIconstInsn(mv, cst);
+ }
+
+ private static void emitIconstInsn(MethodVisitor mv, int cst) {
if (cst >= -1 && cst <= 5) {
mv.visitInsn(Opcodes.ICONST_0 + cst);
} else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) {
mv.visitIntInsn(Opcodes.BIPUSH, cst);
} else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) {
@@ -575,12 +641,11 @@
}
if (isStaticallyNameable(cls)) {
String sig = getInternalName(cls);
mv.visitTypeInsn(Opcodes.CHECKCAST, sig);
} else {
- mv.visitLdcInsn(constantPlaceholder(cls));
- mv.visitTypeInsn(Opcodes.CHECKCAST, CLS);
+ mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(cls), "Ljava/lang/Class;");
mv.visitInsn(Opcodes.SWAP);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLS, "cast", LL_SIG, false);
if (Object[].class.isAssignableFrom(cls))
mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY);
else if (PROFILE_LEVEL > 0)
@@ -735,10 +800,11 @@
* Generate an invoker method for the passed {@link LambdaForm}.
*/
private byte[] generateCustomizedCodeBytes() {
classFilePrologue();
addMethod();
+ clinit();
bogusMethod(lambdaForm);
final byte[] classFile = toByteArray();
maybeDump(classFile);
return classFile;
@@ -762,18 +828,18 @@
mv.visitAnnotation(FORCEINLINE_SIG, true);
} else {
mv.visitAnnotation(DONTINLINE_SIG, true);
}
- constantPlaceholder(lambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled.
+ classData(lambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled.
if (lambdaForm.customized != null) {
// Since LambdaForm is customized for a particular MethodHandle, it's safe to substitute
// receiver MethodHandle (at slot #0) with an embedded constant and use it instead.
// It enables more efficient code generation in some situations, since embedded constants
// are compile-time constants for JIT compiler.
- mv.visitLdcInsn(constantPlaceholder(lambdaForm.customized));
+ mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(lambdaForm.customized), MH_SIG);
mv.visitTypeInsn(Opcodes.CHECKCAST, MH);
assert(checkActualReceiver()); // expects MethodHandle on top of the stack
mv.visitVarInsn(Opcodes.ASTORE, localsMap[0]);
}
@@ -899,11 +965,11 @@
assert(!name.isLinkerMethodInvoke()); // should use the static path for these
if (true) {
// push receiver
MethodHandle target = name.function.resolvedHandle();
assert(target != null) : name.exprString();
- mv.visitLdcInsn(constantPlaceholder(target));
+ mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(target), MH_SIG);
emitReferenceCast(MethodHandle.class, target);
} else {
// load receiver
emitAloadInsn(0);
emitReferenceCast(MethodHandle.class, null);
@@ -955,11 +1021,13 @@
return false; // FIXME
if (cls.isAnonymousClass() || cls.isLocalClass())
return false; // inner class of some sort
if (cls.getClassLoader() != MethodHandle.class.getClassLoader())
return false; // not on BCP
- if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: switch to supported API once it is added
+ if (cls.isHiddenClass())
+ return false;
+ if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: Unsafe::defineAnonymousClass to be removed
return false;
if (!isStaticallyInvocableType(member.getMethodOrFieldType()))
return false;
if (!member.isPrivate() && VerifyAccess.isSamePackage(MethodHandle.class, cls))
return true; // in java.lang.invoke package
@@ -979,18 +1047,20 @@
static boolean isStaticallyNameable(Class<?> cls) {
if (cls == Object.class)
return true;
if (MethodHandle.class.isAssignableFrom(cls)) {
- assert(!ReflectUtil.isVMAnonymousClass(cls));
+ assert(!cls.isHiddenClass());
return true;
}
while (cls.isArray())
cls = cls.getComponentType();
if (cls.isPrimitive())
return true; // int[].class, for example
- if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: switch to supported API once it is added
+ if (cls.isHiddenClass())
+ return false;
+ if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: Unsafe::defineAnonymousClass to be removed
return false;
// could use VerifyAccess.isClassAccessible but the following is a safe approximation
if (cls.getClassLoader() != Object.class.getClassLoader())
return false;
if (VerifyAccess.isSamePackage(MethodHandle.class, cls))
@@ -1058,11 +1128,11 @@
} catch (Throwable ex) {
throw uncaughtException(ex);
}
assert(java.lang.reflect.Array.getLength(emptyArray) == 0);
assert(emptyArray.getClass() == rtype); // exact typing
- mv.visitLdcInsn(constantPlaceholder(emptyArray));
+ mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(emptyArray), "Ljava/lang/Object;");
emitReferenceCast(rtype, emptyArray);
return;
}
Class<?> arrayElementType = rtype.getComponentType();
assert(arrayElementType != null);
@@ -1621,11 +1691,11 @@
emitConst(arg);
} else {
if (Wrapper.isWrapperType(arg.getClass()) && bptype != L_TYPE) {
emitConst(arg);
} else {
- mv.visitLdcInsn(constantPlaceholder(arg));
+ mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(arg), "Ljava/lang/Object;");
emitImplicitConversion(L_TYPE, ptype, arg);
}
}
}
@@ -1813,10 +1883,11 @@
// return statement
emitReturnInsn(basicType(rtype));
methodEpilogue();
+ clinit();
bogusMethod(invokerType);
final byte[] classFile = cw.toByteArray();
maybeDump(classFile);
return classFile;
@@ -1881,10 +1952,11 @@
mv.visitInsn(Opcodes.ACONST_NULL);
}
emitReturnInsn(L_TYPE); // NOTE: NamedFunction invokers always return a reference value.
methodEpilogue();
+ clinit();
bogusMethod(dstType);
final byte[] classFile = cw.toByteArray();
maybeDump(classFile);
return classFile;
< prev index next >