< 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 >