< prev index next >

src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java

Print this page
rev 58565 : 8238358: Implementation of JEP 371: Hidden Classes
Reviewed-by: duke
Contributed-by: mandy.chung@oracle.com, lois.foltan@oracle.com, david.holmes@oracle.com, harold.seigel@oracle.com, serguei.spitsyn@oracle.com, alex.buckley@oracle.com, jamsheed.c.m@oracle.com

*** 24,33 **** --- 24,34 ---- */ 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,56 **** --- 41,59 ---- 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.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,74 **** --- 68,79 ---- 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,106 **** --- 95,121 ---- 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,263 **** sfx = "0"+sfx; className += sfx; return className; } ! class CpPatch { ! final int index; final Object value; ! CpPatch(int index, Object value) { ! this.index = index; this.value = value; } public String toString() { ! return "CpPatch/index="+index+",value="+value; } } ! private final ArrayList<CpPatch> cpPatches = new ArrayList<>(); ! ! private int cph = 0; // for counting constant placeholders ! 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; } ! 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; } ! return res; } private static String debugString(Object arg) { if (arg instanceof MethodHandle) { MethodHandle mh = (MethodHandle) arg; --- 234,289 ---- sfx = "0"+sfx; className += sfx; return className; } ! public static class ClassData { ! final String name; ! final String desc; final Object value; ! ! 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 name + ",value="+value; } } ! 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;"; ! } ! 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(); } ! List<Object> classDataValues() { ! Object[] data = new Object[classData.size()]; ! for (int i = 0; i < classData.size(); i++) { ! data[i] = classData.get(i).value; } ! return List.of(data); } private static String debugString(Object arg) { if (arg instanceof MethodHandle) { MethodHandle mh = (MethodHandle) arg;
*** 286,308 **** /** * Extract the MemberName of a newly-defined method. */ private MemberName loadMethod(byte[] classFile) { ! Class<?> invokerClass = loadAndInitializeInvokerClass(classFile, cpPatches(classFile)); 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) { --- 312,326 ---- /** * Extract the MemberName of a newly-defined method. */ private MemberName loadMethod(byte[] classFile) { ! Class<?> invokerClass = LOOKUP.makeHiddenClassDefiner(classFile) ! .defineClass(true, classDataValues()); return resolveInvokerMember(invokerClass, invokerName, invokerType); } 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,324 **** /** * 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); 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; } --- 332,343 ---- /** * Set up class file generation. */ private ClassWriter classFilePrologue() { final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC ! 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,343 **** --- 353,404 ---- 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,415 **** --- 467,480 ---- // 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,586 **** } if (isStaticallyNameable(cls)) { String sig = getInternalName(cls); mv.visitTypeInsn(Opcodes.CHECKCAST, sig); } else { ! mv.visitLdcInsn(constantPlaceholder(cls)); ! mv.visitTypeInsn(Opcodes.CHECKCAST, CLS); 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) --- 640,650 ---- } if (isStaticallyNameable(cls)) { String sig = getInternalName(cls); mv.visitTypeInsn(Opcodes.CHECKCAST, sig); } else { ! 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,744 **** --- 799,809 ---- * 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,779 **** mv.visitAnnotation(FORCEINLINE_SIG, true); } else { mv.visitAnnotation(DONTINLINE_SIG, true); } ! constantPlaceholder(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.visitTypeInsn(Opcodes.CHECKCAST, MH); assert(checkActualReceiver()); // expects MethodHandle on top of the stack mv.visitVarInsn(Opcodes.ASTORE, localsMap[0]); } --- 827,844 ---- mv.visitAnnotation(FORCEINLINE_SIG, true); } else { mv.visitAnnotation(DONTINLINE_SIG, true); } ! 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.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,909 **** 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)); emitReferenceCast(MethodHandle.class, target); } else { // load receiver emitAloadInsn(0); emitReferenceCast(MethodHandle.class, null); --- 964,974 ---- 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.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(target), MH_SIG); emitReferenceCast(MethodHandle.class, target); } else { // load receiver emitAloadInsn(0); emitReferenceCast(MethodHandle.class, null);
*** 955,965 **** 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 return false; if (!isStaticallyInvocableType(member.getMethodOrFieldType())) return false; if (!member.isPrivate() && VerifyAccess.isSamePackage(MethodHandle.class, cls)) return true; // in java.lang.invoke package --- 1020,1032 ---- 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 (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,996 **** static boolean isStaticallyNameable(Class<?> cls) { if (cls == Object.class) return true; if (MethodHandle.class.isAssignableFrom(cls)) { ! assert(!ReflectUtil.isVMAnonymousClass(cls)); 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 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)) --- 1046,1065 ---- static boolean isStaticallyNameable(Class<?> cls) { if (cls == Object.class) return true; if (MethodHandle.class.isAssignableFrom(cls)) { ! assert(!cls.isHiddenClass()); return true; } while (cls.isArray()) cls = cls.getComponentType(); if (cls.isPrimitive()) return true; // int[].class, for example ! 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,1068 **** } catch (Throwable ex) { throw uncaughtException(ex); } assert(java.lang.reflect.Array.getLength(emptyArray) == 0); assert(emptyArray.getClass() == rtype); // exact typing ! mv.visitLdcInsn(constantPlaceholder(emptyArray)); emitReferenceCast(rtype, emptyArray); return; } Class<?> arrayElementType = rtype.getComponentType(); assert(arrayElementType != null); --- 1127,1137 ---- } catch (Throwable ex) { throw uncaughtException(ex); } assert(java.lang.reflect.Array.getLength(emptyArray) == 0); assert(emptyArray.getClass() == rtype); // exact typing ! mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(emptyArray), "Ljava/lang/Object;"); emitReferenceCast(rtype, emptyArray); return; } Class<?> arrayElementType = rtype.getComponentType(); assert(arrayElementType != null);
*** 1621,1631 **** emitConst(arg); } else { if (Wrapper.isWrapperType(arg.getClass()) && bptype != L_TYPE) { emitConst(arg); } else { ! mv.visitLdcInsn(constantPlaceholder(arg)); emitImplicitConversion(L_TYPE, ptype, arg); } } } --- 1690,1700 ---- emitConst(arg); } else { if (Wrapper.isWrapperType(arg.getClass()) && bptype != L_TYPE) { emitConst(arg); } else { ! mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(arg), "Ljava/lang/Object;"); emitImplicitConversion(L_TYPE, ptype, arg); } } }
*** 1813,1822 **** --- 1882,1892 ---- // return statement emitReturnInsn(basicType(rtype)); methodEpilogue(); + clinit(); bogusMethod(invokerType); final byte[] classFile = cw.toByteArray(); maybeDump(classFile); return classFile;
*** 1881,1890 **** --- 1951,1961 ---- 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 >