changeset 436:9193ecd53c82

meth-lfi.patch: passes unit tests
author jrose
date Tue, 16 Oct 2012 00:18:56 -0700
parents bb9c6901db85
children d925ea8227c0
files anno-stable.patch meth-lfi.patch series
diffstat 3 files changed, 1329 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/anno-stable.patch	Mon Oct 15 17:48:49 2012 -0700
+++ b/anno-stable.patch	Tue Oct 16 00:18:56 2012 -0700
@@ -187,7 +187,7 @@
 new file mode 100644
 --- /dev/null
 +++ b/test/sun/invoke/StableValueTest.java
-@@ -0,0 +1,109 @@
+@@ -0,0 +1,113 @@
 +/*
 + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
@@ -296,4 +296,8 @@
 +        }
 +        return x;
 +    }
++
++    @org.junit.Test public void run() {
++        main();
++    }
 +}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/meth-lfi.patch	Tue Oct 16 00:18:56 2012 -0700
@@ -0,0 +1,1323 @@
+Lambda form interning.
+Allows greater sharing of bytecodes.
+Assumes @Stable arrays for optimization.
+
+diff --git a/src/share/classes/java/lang/invoke/DirectMethodHandle.java b/src/share/classes/java/lang/invoke/DirectMethodHandle.java
+--- a/src/share/classes/java/lang/invoke/DirectMethodHandle.java
++++ b/src/share/classes/java/lang/invoke/DirectMethodHandle.java
+@@ -251,14 +251,14 @@
+         lambdaName += "_" + LambdaForm.basicTypeSignature(mtype);
+         LambdaForm lform = new LambdaForm(lambdaName, ARG_LIMIT, names, result);
+         // This is a tricky bit of code.  Don't send it through the LF interpreter.
+-        lform.compileToBytecode();
++        lform.compileToBytecode(true);
+         return lform;
+     }
+ 
+     private static void maybeCompile(LambdaForm lform, MemberName m) {
+         if (VerifyAccess.isSamePackage(m.getDeclaringClass(), MethodHandle.class))
+             // Help along bootstrapping...
+-            lform.compileToBytecode();
++            lform.compileToBytecode(true);
+     }
+ 
+     /** Static wrapper for DirectMethodHandle.internalMemberName. */
+diff --git a/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java b/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
+--- a/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
++++ b/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
+@@ -27,7 +27,6 @@
+ 
+ import sun.invoke.util.VerifyAccess;
+ import java.lang.invoke.LambdaForm.Name;
+-import java.lang.invoke.MethodHandles.Lookup;
+ 
+ import sun.invoke.util.Wrapper;
+ 
+@@ -35,12 +34,12 @@
+ import java.util.*;
+ 
+ import com.sun.xml.internal.ws.org.objectweb.asm.*;
++import java.lang.invoke.LambdaForm.Template;
+ 
+ import java.lang.reflect.*;
+ import static java.lang.invoke.MethodHandleStatics.*;
+ import static java.lang.invoke.MethodHandleNatives.Constants.*;
+-import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
+-import sun.invoke.util.ValueConversions;
++import java.util.concurrent.ConcurrentHashMap;
+ import sun.invoke.util.VerifyType;
+ 
+ /**
+@@ -51,13 +50,13 @@
+ class InvokerBytecodeGenerator {
+     /** Define class names for convenience. */
+     private static final String MH      = "java/lang/invoke/MethodHandle";
+-    private static final String BMH     = "java/lang/invoke/BoundMethodHandle";
+     private static final String LF      = "java/lang/invoke/LambdaForm";
+     private static final String LFN     = "java/lang/invoke/LambdaForm$Name";
+     private static final String CLS     = "java/lang/Class";
+     private static final String OBJ     = "java/lang/Object";
+     private static final String OBJARY  = "[Ljava/lang/Object;";
+ 
++    private static final String OBJ_SIG = "L" + OBJ + ";";
+     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 + ";";
+@@ -72,9 +71,11 @@
+     private final String sourceFile;
+ 
+     private final LambdaForm lambdaForm;
++    private final Template   lambdaFormTemplate;
+     private final String     invokerName;
+     private final MethodType invokerType;
+     private final int[] localsMap;
++    private final Object[] localsModel;
+ 
+     /** ASM bytecode generation. */
+     private ClassWriter cw;
+@@ -83,7 +84,7 @@
+     private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory();
+     private static final Class<?> HOST_CLASS = LambdaForm.class;
+ 
+-    private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize,
++    private InvokerBytecodeGenerator(Template temp, LambdaForm form, int localsMapSize,
+                                      String className, String invokerName, MethodType invokerType) {
+         if (invokerName.contains(".")) {
+             int p = invokerName.indexOf(".");
+@@ -95,14 +96,16 @@
+         }
+         this.className  = superName + "$" + className;
+         this.sourceFile = "LambdaForm$" + className;
+-        this.lambdaForm = lambdaForm;
++        this.lambdaFormTemplate = temp;
++        this.lambdaForm = form;
+         this.invokerName = invokerName;
+         this.invokerType = invokerType;
+         this.localsMap = new int[localsMapSize];
++        this.localsModel = new Object[localsMapSize];
+     }
+ 
+     private InvokerBytecodeGenerator(String className, String invokerName, MethodType invokerType) {
+-        this(null, invokerType.parameterCount(),
++        this(null, null, invokerType.parameterCount(),
+              className, invokerName, invokerType);
+         // Create an array to map name indexes to locals indexes.
+         for (int i = 0; i < localsMap.length; i++) {
+@@ -110,17 +113,27 @@
+         }
+     }
+ 
+-    private InvokerBytecodeGenerator(String className, LambdaForm form, MethodType invokerType) {
+-        this(form, form.names.length,
+-             className, form.debugName, invokerType);
++    private InvokerBytecodeGenerator(String className, Template temp, LambdaForm form) {
++        this(temp, form, form.names.length + SCRATCH_LOCALS,
++             className, form.debugName, form.methodType());
+         // Create an array to map name indexes to locals indexes.
++        assert(temp == null || temp.equals(new Template(form)));
+         Name[] names = form.names;
+         for (int i = 0, index = 0; i < localsMap.length; i++) {
+             localsMap[i] = index;
+-            index += Wrapper.forBasicType(names[i].type).stackSlots();
++            char type = (i < names.length ? names[i].type : 'L');
++            index += Wrapper.forBasicType(type).stackSlots();
+         }
++        // final value of index is number of locals, but we don't need it
+     }
+ 
++    private static final int SCRATCH_LOCALS = 2;  // LambdaForm, Name.arguments
++    private int localThisLF() {
++        return localsMap.length-2;
++    }
++    private int localThisArgList() {
++        return localsMap.length-1;
++    }
+ 
+     /** instance counters for dumped classes */
+     private final static HashMap<String,Integer> DUMP_CLASS_FILES_COUNTERS;
+@@ -146,6 +159,35 @@
+         }
+     }
+ 
++    static HashSet<LambdaForm.Template> DUMPED_TEMPLATES = null;
++    void maybeDumpTemplate(final String className) {
++        if (DUMP_CLASS_FILES) {
++            if (lambdaForm != null) {
++                final LambdaForm.Template temp = new LambdaForm.Template(lambdaForm);
++                if (DUMPED_TEMPLATES == null)
++                    DUMPED_TEMPLATES = new HashSet<>();
++                if (!DUMPED_TEMPLATES.add(temp))
++                    return;
++                java.security.AccessController.doPrivileged(
++                new java.security.PrivilegedAction<Void>() {
++                    public Void run() {
++                        try {
++                            String dumpName = className;
++                            //dumpName = dumpName.replace('/', '-');
++                            File dumpFile = new File(DUMP_CLASS_FILES_DIR, dumpName+".template");
++                            dumpFile.getParentFile().mkdirs();
++                            FileOutputStream file = new FileOutputStream(dumpFile);
++                            file.write(temp.toString().getBytes());
++                            file.close();
++                            return null;
++                        } catch (IOException ex) {
++                            throw new InternalError(ex);
++                        }
++                    }
++                });
++            }
++        }
++    }
+     static void maybeDump(final String className, final byte[] classFile) {
+         if (DUMP_CLASS_FILES) {
+             System.out.println("dump: " + className);
+@@ -416,8 +458,8 @@
+      *
+      * @param type primitive type class to box.
+      */
+-    private void emitBoxing(Class<?> type) {
+-        Wrapper wrapper = Wrapper.forPrimitiveType(type);
++    private void emitBoxing(Wrapper wrapper) {
++        if (wrapper == Wrapper.OBJECT)  return;
+         String owner = "java/lang/" + wrapper.wrapperType().getSimpleName();
+         String name  = "valueOf";
+         String desc  = "(" + wrapper.basicTypeChar() + ")L" + owner + ";";
+@@ -429,8 +471,8 @@
+      *
+      * @param type wrapper type class to unbox.
+      */
+-    private void emitUnboxing(Class<?> type) {
+-        Wrapper wrapper = Wrapper.forWrapperType(type);
++    private void emitUnboxing(Wrapper wrapper) {
++        if (wrapper == Wrapper.OBJECT)  return;
+         String owner = "java/lang/" + wrapper.wrapperType().getSimpleName();
+         String name  = wrapper.primitiveSimpleName() + "Value";
+         String desc  = "()" + wrapper.basicTypeChar();
+@@ -439,6 +481,27 @@
+     }
+ 
+     /**
++     * Emit a checkcast, if the reference type is non-trivial.
++     *
++     * @param type reference type required.
++     */
++    private void emitRefCast(Class<?> type) {
++        assert(!type.isPrimitive());
++        if (VerifyType.isNullConversion(Object.class, type))
++            return;
++        if (isStaticallyNameable(type)) {
++            mv.visitTypeInsn(Opcodes.CHECKCAST, getInternalName(type));
++        } else {
++            mv.visitLdcInsn(constantPlaceholder(type));
++            mv.visitTypeInsn(Opcodes.CHECKCAST, CLS);
++            mv.visitInsn(Opcodes.SWAP);
++            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLS, "cast", LL_SIG);
++            if (type.isArray())
++                mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY);
++        }
++    }
++
++    /**
+      * Emit an implicit conversion.
+      *
+      * @param ptype type of value present on stack
+@@ -447,22 +510,11 @@
+     private void emitImplicitConversion(char ptype, Class<?> pclass) {
+         switch (ptype) {
+         case 'L':
+-            if (VerifyType.isNullConversion(Object.class, pclass))
+-                return;
+-            if (isStaticallyNameable(pclass)) {
+-                mv.visitTypeInsn(Opcodes.CHECKCAST, getInternalName(pclass));
+-            } else {
+-                mv.visitLdcInsn(constantPlaceholder(pclass));
+-                mv.visitTypeInsn(Opcodes.CHECKCAST, CLS);
+-                mv.visitInsn(Opcodes.SWAP);
+-                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLS, "cast", LL_SIG);
+-                if (pclass.isArray())
+-                    mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY);
+-            }
++            emitRefCast(pclass);
+             return;
+         case 'I':
+             if (!VerifyType.isNullConversion(int.class, pclass))
+-                emitPrimCast(ptype, Wrapper.basicTypeChar(pclass));
++                emitPrimCast(Wrapper.INT, Wrapper.forPrimitiveType(pclass));
+             return;
+         case 'J':
+             assert(pclass == long.class);
+@@ -500,6 +552,17 @@
+         return c.getName().replace('.', '/');
+     }
+ 
++    private static final ConcurrentHashMap<Template,MemberName> COMPILED_TEMPLATES;
++    static {
++        int   capacity   = 512;    // expect many distinct signatures over time
++        float loadFactor = 0.75f;  // normal default
++        int   writers    = 1;
++        if (BYTECODE_SHARE_LEVEL != null && (int)BYTECODE_SHARE_LEVEL == 0)
++            COMPILED_TEMPLATES = null;
++        else
++            COMPILED_TEMPLATES = new ConcurrentHashMap<>(capacity, loadFactor, writers);
++    }
++
+     /**
+      * Generate customized bytecode for a given LambdaForm.
+      *
+@@ -507,9 +570,26 @@
+      * @param invokerType
+      * @return
+      */
+-    static MemberName generateCustomizedCode(LambdaForm form, MethodType invokerType) {
+-        InvokerBytecodeGenerator g = new InvokerBytecodeGenerator("MH", form, invokerType);
+-        return g.loadMethod(g.generateCustomizedCodeBytes());
++    static MemberName generateCustomizedCode(Template temp, LambdaForm form) {
++        if (temp != null && COMPILED_TEMPLATES != null) {
++            MemberName compiled = COMPILED_TEMPLATES.get(temp);
++            if (compiled != null) {
++                return compiled;
++            }
++            // No hit.  Got to generate new code.
++            temp = temp.copyFactored();
++        }
++        InvokerBytecodeGenerator g = new InvokerBytecodeGenerator("MH", temp, form);
++        MemberName vmentry = g.loadMethod(g.generateCustomizedCodeBytes());
++        if (temp != null && COMPILED_TEMPLATES != null) {
++            MemberName vmentry2 = COMPILED_TEMPLATES.putIfAbsent(temp, vmentry);
++            if (vmentry2 != null)  vmentry = vmentry2;  // another racer won
++        }
++        return vmentry;
++    }
++
++    private boolean shouldBeFactored(Name name, int j) {
++        return lambdaFormTemplate != null && lambdaFormTemplate.shouldBeFactored(name, j);
+     }
+ 
+     /**
+@@ -530,7 +610,9 @@
+             Name name = lambdaForm.names[i];
+             MemberName member = name.function.member();
+ 
+-            if (isSelectAlternative(member)) {
++            if (shouldBeFactored(name, -1)) {
++                emitInvoke(name, true);
++            } else if (isSelectAlternative(member) && lambdaFormTemplate != null && name.arguments[0] instanceof Name) {
+                 // selectAlternative idiom
+                 // FIXME: make sure this idiom is really present!
+                 emitSelectAlternative(name, lambdaForm.names[i + 1]);
+@@ -538,7 +620,7 @@
+             } else if (isStaticallyInvocable(member)) {
+                 emitStaticInvoke(member, name);
+             } else {
+-                emitInvoke(name);
++                emitInvoke(name, false);
+             }
+ 
+             // store the result from evaluating to the target name in a local if required
+@@ -560,28 +642,65 @@
+ 
+         final byte[] classFile = cw.toByteArray();
+         maybeDump(className, classFile);
++        maybeDumpTemplate(className);
+         return classFile;
+     }
+ 
++    void emitLoadThisLambdaForm() {
++        boolean cache = true;
++        int index = localThisLF();
++        if (localsModel[index] == lambdaForm) {
++            emitAloadInsn(index);
++        } else {
++            emitAloadInsn(0);
++            if (invokerType.parameterType(0) != MethodHandle.class)
++                mv.visitTypeInsn(Opcodes.CHECKCAST, MH);
++            mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", LF_SIG);
++            if (cache) {
++                mv.visitInsn(Opcodes.DUP);
++                emitAstoreInsn(index);
++                localsModel[index] = lambdaForm;
++            }
++        }
++    }
++
++    void emitLoadThisArgList(Name name) {
++        boolean cache = true;
++        int index = localThisArgList();
++        Object[] arglist = name.arguments;
++        if (localsModel[index] == arglist) {
++            emitAloadInsn(index);
++        } else {
++            emitLoadThisLambdaForm();
++            mv.visitFieldInsn(Opcodes.GETFIELD, LF, "names", "[" + LFN_SIG);
++            emitIconstInsn(name.index());
++            mv.visitInsn(Opcodes.AALOAD);
++            mv.visitFieldInsn(Opcodes.GETFIELD, LFN, "arguments", "[" + OBJ_SIG);
++            if (cache) {
++                mv.visitInsn(Opcodes.DUP);
++                emitAstoreInsn(index);
++                localsModel[index] = arglist;
++            }
++        }
++    }
++
+     /**
+      * Emit an invoke for the given name.
+      *
+      * @param name
+      */
+-    void emitInvoke(Name name) {
+-        if (true) {
++    void emitInvoke(Name name, boolean shouldBeFactored) {
++        if (!shouldBeFactored) {
+             // push receiver
+             MethodHandle target = name.function.resolvedHandle;
+             assert(target != null) : name.exprString();
+             mv.visitLdcInsn(constantPlaceholder(target));
+             mv.visitTypeInsn(Opcodes.CHECKCAST, MH);
+         } else {
+-            // load receiver
+-            emitAloadInsn(0);
+-            mv.visitTypeInsn(Opcodes.CHECKCAST, MH);
+-            mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", LF_SIG);
+-            mv.visitFieldInsn(Opcodes.GETFIELD, LF, "names", LFN_SIG);
+-            // TODO more to come
++            // load receiver from the MH.form
++            emitLoadThisLambdaForm();
++            emitIconstInsn(name.index());
++            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, LF, "getResolvedHandleAt", "(I)L" + MH + ";");
+         }
+ 
+         // push arguments
+@@ -708,8 +827,6 @@
+      * @param invokeBasicName
+      */
+     private void emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) {
+-        MethodType type = selectAlternativeName.function.methodType();
+-
+         Name receiver = (Name) invokeBasicName.arguments[0];
+ 
+         Label L_fallback = new Label();
+@@ -723,10 +840,9 @@
+         mv.visitJumpInsn(Opcodes.IF_ICMPNE, L_fallback);
+ 
+         // invoke selectAlternativeName.arguments[1]
+-        MethodHandle target = (MethodHandle) selectAlternativeName.arguments[1];
+         emitPushArgument(selectAlternativeName, 1);  // get 2nd argument of selectAlternative
+         emitAstoreInsn(receiver.index());  // store the MH in the receiver slot
+-        emitInvoke(invokeBasicName);
++        emitInvoke(invokeBasicName, shouldBeFactored(invokeBasicName, -1));
+ 
+         // goto L_done
+         mv.visitJumpInsn(Opcodes.GOTO, L_done);
+@@ -735,10 +851,9 @@
+         mv.visitLabel(L_fallback);
+ 
+         // invoke selectAlternativeName.arguments[2]
+-        MethodHandle fallback = (MethodHandle) selectAlternativeName.arguments[2];
+         emitPushArgument(selectAlternativeName, 2);  // get 3rd argument of selectAlternative
+         emitAstoreInsn(receiver.index());  // store the MH in the receiver slot
+-        emitInvoke(invokeBasicName);
++        emitInvoke(invokeBasicName, shouldBeFactored(invokeBasicName, -1));
+ 
+         // L_done:
+         mv.visitLabel(L_done);
+@@ -753,10 +868,21 @@
+         Object arg = name.arguments[paramIndex];
+         char ptype = name.function.parameterType(paramIndex);
+         MethodType mtype = name.function.methodType();
++        if (shouldBeFactored(name, -1))
++            mtype = mtype.basicType();
++        Class<?> parameterType = mtype.parameterType(paramIndex);
+         if (arg instanceof Name) {
+             Name n = (Name) arg;
+             emitLoadInsn(n.type, n.index());
+-            emitImplicitConversion(n.type, mtype.parameterType(paramIndex));
++            emitImplicitConversion(n.type, parameterType);
++        } else if (shouldBeFactored(name, paramIndex)) {
++            emitLoadThisArgList(name);
++            emitIconstInsn(paramIndex);
++            mv.visitInsn(Opcodes.AALOAD);
++            if (parameterType.isPrimitive())
++                emitUnboxing(Wrapper.forBasicType(ptype));
++            else
++                emitImplicitConversion('L', parameterType);
+         } else if ((arg == null || arg instanceof String) && ptype == 'L') {
+             emitConst(arg);
+         } else {
+@@ -764,7 +890,7 @@
+                 emitConst(arg);
+             } else {
+                 mv.visitLdcInsn(constantPlaceholder(arg));
+-                emitImplicitConversion('L', mtype.parameterType(paramIndex));
++                emitImplicitConversion('L', parameterType);
+             }
+         }
+     }
+@@ -774,52 +900,66 @@
+      */
+     private void emitReturn() {
+         // return statement
+-        if (lambdaForm.result == -1) {
++        Class<?> reqType = invokerType.returnType();
++        char reqbt = Wrapper.basicTypeChar(reqType);
++        if (reqType == void.class) {
+             // void
+-            mv.visitInsn(Opcodes.RETURN);
++        } else if (lambdaForm.result == -1) {
++            emitConst(Wrapper.forBasicType(reqbt).zero());
+         } else {
+             LambdaForm.Name rn = lambdaForm.names[lambdaForm.result];
+-            char rtype = Wrapper.basicTypeChar(invokerType.returnType());
+ 
+             // put return value on the stack if it is not already there
+             if (lambdaForm.result != lambdaForm.names.length - 1) {
+                 emitLoadInsn(rn.type, lambdaForm.result);
+             }
+ 
++            char tosbt = rn.type();
++            Class<?> tosType = LambdaForm.typeClass(tosbt);
++            if (!rn.isParam())
++                tosType = rn.function.methodType().returnType();
++
+             // potentially generate cast
+             // rtype is the return type of the invoker - generated code must conform to this
+             // rn.type is the type of the result Name in the LF
+-            if (rtype != rn.type) {
++            if (tosbt != reqbt) {
+                 // need cast
+-                if (rtype == 'L') {
++                Wrapper w;
++                if (reqbt == 'L') {
+                     // possibly cast the primitive to the correct type for boxing
+-                    char boxedType = Wrapper.forWrapperType(invokerType.returnType()).basicTypeChar();
+-                    if (boxedType != rn.type) {
+-                        emitPrimCast(rn.type, boxedType);
+-                    }
++                    if (reqType == Object.class || !Wrapper.isWrapperType(reqType))
++                        w = Wrapper.forPrimitiveType(tosType);
++                    else
++                        w = Wrapper.forWrapperType(reqType);
++                    if (tosType != w.primitiveType())
++                        emitPrimCast(Wrapper.forBasicType(tosbt), w);
+                     // cast primitive to reference ("boxing")
+-                    emitBoxing(invokerType.returnType());
+-                } else {
+-                    // to-primitive cast
+-                    if (rn.type != 'L') {
+-                        // prim-to-prim cast
+-                        emitPrimCast(rn.type, rtype);
+-                    } else {
+-                        // ref-to-prim cast ("unboxing")
+-                        throw new InternalError("no ref-to-prim (unboxing) casts supported right now");
+-                    }
++                    emitBoxing(w);
++                    tosType = w.wrapperType();
++                } else if (tosbt == 'L') {
++                    w = Wrapper.forBasicType(reqbt);
++                    // ref-to-prim cast ("unboxing")
++                    emitUnboxing(w);
++                    tosType = w.primitiveType();
++                } else if (tosType != reqType) {
++                    // prim-to-prim cast
++                    emitPrimCast(Wrapper.forBasicType(tosbt), Wrapper.forBasicType(reqbt));
++                    tosType = reqType;
+                 }
+             }
+-
+-            // generate actual return statement
+-            emitReturnInsn(invokerType.returnType());
++            if (!VerifyType.isNullConversion(tosType, reqType)) {
++                assert(reqbt == 'L');
++                emitRefCast(reqType);
++            }
+         }
++        // generate actual return statement
++        emitReturnInsn(reqType);
+     }
+ 
+     /**
+      * Emit a type conversion bytecode casting from "from" to "to".
+      */
+-    private void emitPrimCast(char from, char to) {
++    private void emitPrimCast(Wrapper wfrom, Wrapper wto) {
+         // Here's how.
+         // -   indicates forbidden
+         // <-> indicates implicit
+@@ -832,12 +972,11 @@
+         //      long        -     l2i,i2b   l2i,i2s  l2i,i2c    l2i      <->      l2f      l2d
+         //      float       -     f2i,i2b   f2i,i2s  f2i,i2c    f2i      f2l      <->      f2d
+         //      double      -     d2i,i2b   d2i,i2s  d2i,i2c    d2i      d2l      d2f      <->
+-        if (from == to) {
++        if (wfrom == wto) {
+             // no cast required, should be dead code anyway
+             return;
+         }
+-        Wrapper wfrom = Wrapper.forBasicType(from);
+-        Wrapper wto   = Wrapper.forBasicType(to);
++        char from = wfrom.basicTypeChar(), to = wto.basicTypeChar();
+         if (wfrom.isSubwordOrInt()) {
+             // cast from {byte,short,char,int} to anything
+             emitI2X(to);
+@@ -955,20 +1094,20 @@
+             emitLoadInsn(Wrapper.basicTypeChar(ptype), i);
+             // box if primitive type
+             if (ptype.isPrimitive()) {
+-                emitBoxing(ptype);
++                emitBoxing(Wrapper.forPrimitiveType(ptype));
+             }
+             mv.visitInsn(Opcodes.AASTORE);
+         }
+         // invoke
+         emitAloadInsn(0);
+-        mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", "Ljava/lang/invoke/LambdaForm;");
++        mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", LF_SIG);
+         mv.visitInsn(Opcodes.SWAP);  // swap form and array; avoid local variable
+         mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, LF, "interpretWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;");
+ 
+         // maybe unbox
+         Class<?> rtype = invokerType.returnType();
+         if (rtype.isPrimitive() && rtype != void.class) {
+-            emitUnboxing(Wrapper.asWrapperType(rtype));
++            emitUnboxing(Wrapper.forPrimitiveType(rtype));
+         }
+ 
+         // return statement
+@@ -1020,8 +1159,8 @@
+                 Class<?> sptype = dstType.basicType().wrap().parameterType(i);
+                 Wrapper dstWrapper = Wrapper.forBasicType(dptype);
+                 Wrapper srcWrapper = dstWrapper.isSubwordOrInt() ? Wrapper.INT : dstWrapper;  // narrow subword from int
+-                emitUnboxing(srcWrapper.wrapperType());
+-                emitPrimCast(srcWrapper.basicTypeChar(), dstWrapper.basicTypeChar());
++                emitUnboxing(srcWrapper);
++                emitPrimCast(srcWrapper, dstWrapper);
+             }
+         }
+ 
+@@ -1035,8 +1174,8 @@
+             Wrapper srcWrapper = Wrapper.forBasicType(rtype);
+             Wrapper dstWrapper = srcWrapper.isSubwordOrInt() ? Wrapper.INT : srcWrapper;  // widen subword to int
+             // boolean casts not allowed
+-            emitPrimCast(srcWrapper.basicTypeChar(), dstWrapper.basicTypeChar());
+-            emitBoxing(dstWrapper.primitiveType());
++            emitPrimCast(srcWrapper, dstWrapper);
++            emitBoxing(dstWrapper);
+         }
+ 
+         // If the return type is void we return a null reference.
+diff --git a/src/share/classes/java/lang/invoke/Invokers.java b/src/share/classes/java/lang/invoke/Invokers.java
+--- a/src/share/classes/java/lang/invoke/Invokers.java
++++ b/src/share/classes/java/lang/invoke/Invokers.java
+@@ -336,7 +336,7 @@
+         names[LINKER_CALL] = new Name(invokeBasicMethod(outCallType), outArgs);
+         lform = new LambdaForm(debugName, INARG_LIMIT, names);
+         if (isLinker)
+-            lform.compileToBytecode();  // JVM needs a real methodOop
++            lform.compileToBytecode(true);  // JVM needs a real methodOop
+         if (isCached)
+             lform = mtype.form().setCachedLambdaForm(which, lform);
+         return lform;
+@@ -424,7 +424,7 @@
+         outArgs[PREPEND_MH] = names[CALL_MH];
+         names[LINKER_CALL] = new Name(invokeBasicMethod(mtype), outArgs);
+         lform = new LambdaForm("linkToCallSite", INARG_LIMIT, names);
+-        lform.compileToBytecode();  // JVM needs a real methodOop
++        lform.compileToBytecode(true);  // JVM needs a real methodOop
+         lform = mtype.form().setCachedLambdaForm(MethodTypeForm.LF_CS_LINKER, lform);
+         return lform;
+     }
+diff --git a/src/share/classes/java/lang/invoke/LambdaForm.java b/src/share/classes/java/lang/invoke/LambdaForm.java
+--- a/src/share/classes/java/lang/invoke/LambdaForm.java
++++ b/src/share/classes/java/lang/invoke/LambdaForm.java
+@@ -27,18 +27,13 @@
+ 
+ import java.lang.annotation.*;
+ import java.lang.reflect.Method;
+-import java.util.Map;
+ import java.util.List;
+ import java.util.Arrays;
+-import java.util.ArrayList;
+-import java.util.HashMap;
+-import java.util.concurrent.ConcurrentHashMap;
+ import sun.invoke.util.Wrapper;
+ import sun.invoke.Stable;
+ import static java.lang.invoke.MethodHandleStatics.*;
+ import static java.lang.invoke.MethodHandleNatives.Constants.*;
+ import java.lang.reflect.Field;
+-import java.util.Objects;
+ 
+ /**
+  * The symbolic, non-executable form of a method handle's invocation semantics.
+@@ -124,7 +119,7 @@
+     @Stable final Name[] names;
+     final String debugName;
+     MemberName vmentry;   // low-level behavior, or null if not yet prepared
+-    private boolean isCompiled;
++    private byte compileLevel;  // 0: interp, 1: normal, 2: full-custom
+ 
+     // Caches for common structural transforms:
+     LambdaForm[] bindCache;
+@@ -191,12 +186,12 @@
+     }
+ 
+     private static int fixResult(int result, Name[] names) {
+-        if (result >= 0) {
+-            if (names[result].type == 'V')
+-                return -1;
+-        } else if (result == LAST_RESULT) {
+-            return names.length - 1;
+-        }
++        // Normalize the result field to either one of non-void names or -1.
++        if (result == LAST_RESULT)
++          result = names.length - 1;
++        assert(result >= VOID_RESULT);
++        if (result != VOID_RESULT && names[result].type == 'V')
++            result = VOID_RESULT;
+         return result;
+     }
+ 
+@@ -312,9 +307,16 @@
+         return arity;
+     }
+ 
++    /** Report the lead argument type.
++     *  This is usually MethodHandle but could also be an interface wrapper type.
++     */
++    Class<?> receiver() {
++        return MethodHandle.class;
++    }
++
+     /** Return the method type corresponding to my basic type signature. */
+     MethodType methodType() {
+-        return signatureType(basicTypeSignature());
++        return signatureType(basicTypeSignature()).changeParameterType(0, receiver());
+     }
+     /** Return ABC_Z, where the ABC are parameter type characters, and Z is the return type character. */
+     final String basicTypeSignature() {
+@@ -433,7 +435,7 @@
+      */
+     public void prepare() {
+         if (COMPILE_THRESHOLD == 0) {
+-            compileToBytecode();
++            compileToBytecode(false);
+         }
+         if (this.vmentry != null) {
+             // already prepared (e.g., a primitive DMH invoker form)
+@@ -445,143 +447,35 @@
+     }
+ 
+     /** Generate optimizable bytecode for this form. */
+-    MemberName compileToBytecode() {
+-        MethodType invokerType = methodType();
+-        assert(vmentry == null || vmentry.getMethodType().basicType().equals(invokerType));
+-        if (vmentry != null && isCompiled) {
++    MemberName compileToBytecode(boolean fullCustom) {
++        // FIXME: compileLevel should be an immutable property of template and/or vmentry
++        byte requiredLevel = (byte)(fullCustom ? 2 : 1);
++        assert(vmentry == null || vmentry.getMethodType().basicType().equals(methodType().basicType()));
++        if (vmentry != null && compileLevel >= requiredLevel) {
+             return vmentry;  // already compiled somehow
+         }
+         try {
+-            vmentry = InvokerBytecodeGenerator.generateCustomizedCode(this, invokerType);
++            Template temp = fullCustom ? null : new Template(this);
++            vmentry = InvokerBytecodeGenerator.generateCustomizedCode(temp, this);
+             if (TRACE_INTERPRETER)
+-                traceInterpreter("compileToBytecode", this);
+-            isCompiled = true;
++                traceInterpreter("compileToBytecode" + (fullCustom ? "/fullCustom" : ""), this);
++            compileLevel = requiredLevel;
+             return vmentry;
+         } catch (Error | Exception ex) {
+             throw new InternalError(this.toString(), ex);
+         }
+     }
+ 
+-    private static final ConcurrentHashMap<String,LambdaForm> PREPARED_FORMS;
+-    static {
+-        int   capacity   = 512;    // expect many distinct signatures over time
+-        float loadFactor = 0.75f;  // normal default
+-        int   writers    = 1;
+-        PREPARED_FORMS = new ConcurrentHashMap<>(capacity, loadFactor, writers);
+-    }
+-
+-    private static Map<String,LambdaForm> computeInitialPreparedForms() {
+-        // Find all predefined invokers and associate them with canonical empty lambda forms.
+-        HashMap<String,LambdaForm> forms = new HashMap<>();
+-        for (MemberName m : MemberName.getFactory().getMethods(LambdaForm.class, false, null, null, null)) {
+-            if (!m.isStatic() || !m.isPackage())  continue;
+-            MethodType mt = m.getMethodType();
+-            if (mt.parameterCount() > 0 &&
+-                mt.parameterType(0) == MethodHandle.class &&
+-                m.getName().startsWith("interpret_")) {
+-                String sig = basicTypeSignature(mt);
+-                assert(m.getName().equals("interpret" + sig.substring(sig.indexOf('_'))));
+-                LambdaForm form = new LambdaForm(sig);
+-                form.vmentry = m;
+-                mt.form().setCachedLambdaForm(MethodTypeForm.LF_COUNTER, form);
+-                // FIXME: get rid of PREPARED_FORMS; use MethodTypeForm cache only
+-                forms.put(sig, form);
+-            }
+-        }
+-        //System.out.println("computeInitialPreparedForms => "+forms);
+-        return forms;
+-    }
+-
+-    // Set this false to disable use of the interpret_L methods defined in this file.
+-    private static final boolean USE_PREDEFINED_INTERPRET_METHODS = true;
+-
+-    // The following are predefined exact invokers.  The system must build
+-    // a separate invoker for each distinct signature.
+-    static Object interpret_L(MethodHandle mh) throws Throwable {
+-        Object[] av = {mh};
+-        String sig = null;
+-        assert(argumentTypesMatch(sig = "L_L", av));
+-        Object res = mh.form.interpretWithArguments(av);
+-        assert(returnTypesMatch(sig, av, res));
+-        return res;
+-    }
+-    static Object interpret_L(MethodHandle mh, Object x1) throws Throwable {
+-        Object[] av = {mh, x1};
+-        String sig = null;
+-        assert(argumentTypesMatch(sig = "LL_L", av));
+-        Object res = mh.form.interpretWithArguments(av);
+-        assert(returnTypesMatch(sig, av, res));
+-        return res;
+-    }
+-    static Object interpret_L(MethodHandle mh, Object x1, Object x2) throws Throwable {
+-        Object[] av = {mh, x1, x2};
+-        String sig = null;
+-        assert(argumentTypesMatch(sig = "LLL_L", av));
+-        Object res = mh.form.interpretWithArguments(av);
+-        assert(returnTypesMatch(sig, av, res));
+-        return res;
+-    }
+     private static LambdaForm getPreparedForm(String sig) {
+         MethodType mtype = signatureType(sig);
+-        //LambdaForm prep = PREPARED_FORMS.get(sig);
+         LambdaForm prep =  mtype.form().cachedLambdaForm(MethodTypeForm.LF_INTERPRET);
+         if (prep != null)  return prep;
+         assert(isValidSignature(sig));
+         prep = new LambdaForm(sig);
+         prep.vmentry = InvokerBytecodeGenerator.generateLambdaFormInterpreterEntryPoint(sig);
+-        //LambdaForm prep2 = PREPARED_FORMS.putIfAbsent(sig.intern(), prep);
+         return mtype.form().setCachedLambdaForm(MethodTypeForm.LF_INTERPRET, prep);
+     }
+ 
+-    // The next few routines are called only from assert expressions
+-    // They verify that the built-in invokers process the correct raw data types.
+-    private static boolean argumentTypesMatch(String sig, Object[] av) {
+-        int arity = signatureArity(sig);
+-        assert(av.length == arity) : "av.length == arity: av.length=" + av.length + ", arity=" + arity;
+-        assert(av[0] instanceof MethodHandle) : "av[0] not instace of MethodHandle: " + av[0];
+-        MethodHandle mh = (MethodHandle) av[0];
+-        MethodType mt = mh.type();
+-        assert(mt.parameterCount() == arity-1);
+-        for (int i = 0; i < av.length; i++) {
+-            Class<?> pt = (i == 0 ? MethodHandle.class : mt.parameterType(i-1));
+-            assert(valueMatches(sig.charAt(i), pt, av[i]));
+-        }
+-        return true;
+-    }
+-    private static boolean valueMatches(char tc, Class<?> type, Object x) {
+-        // The following line is needed because (...)void method handles can use non-void invokers
+-        if (type == void.class)  tc = 'V';   // can drop any kind of value
+-        assert tc == basicType(type) : tc + " == basicType(" + type + ")=" + basicType(type);
+-        switch (tc) {
+-        case 'I': assert checkInt(type, x)   : "checkInt(" + type + "," + x +")";   break;
+-        case 'J': assert x instanceof Long   : "instanceof Long: " + x;             break;
+-        case 'F': assert x instanceof Float  : "instanceof Float: " + x;            break;
+-        case 'D': assert x instanceof Double : "instanceof Double: " + x;           break;
+-        case 'L': assert checkRef(type, x)   : "checkRef(" + type + "," + x + ")";  break;
+-        case 'V': break;  // allow anything here; will be dropped
+-        default:  assert(false);
+-        }
+-        return true;
+-    }
+-    private static boolean returnTypesMatch(String sig, Object[] av, Object res) {
+-        MethodHandle mh = (MethodHandle) av[0];
+-        return valueMatches(signatureReturn(sig), mh.type().returnType(), res);
+-    }
+-    private static boolean checkInt(Class<?> type, Object x) {
+-        assert(x instanceof Integer);
+-        if (type == int.class)  return true;
+-        Wrapper w = Wrapper.forBasicType(type);
+-        assert(w.isSubwordOrInt());
+-        Object x1 = Wrapper.INT.wrap(w.wrap(x));
+-        return x.equals(x1);
+-    }
+-    private static boolean checkRef(Class<?> type, Object x) {
+-        assert(!type.isPrimitive());
+-        if (x == null)  return true;
+-        if (type.isInterface())  return true;
+-        return type.isInstance(x);
+-    }
+-
+     /** If the invocation count hits the threshold we spin bytecodes and call that subsequently. */
+     private static final int COMPILE_THRESHOLD;
+     static {
+@@ -627,22 +521,18 @@
+     private void checkInvocationCounter() {
+         if (COMPILE_THRESHOLD != 0 &&
+             invocationCounter < COMPILE_THRESHOLD) {
+-            invocationCounter++;  // benign race
+-            if (invocationCounter >= COMPILE_THRESHOLD) {
++            int ctr = ++invocationCounter;  // benign race
++            if (TRACE_INTERPRETER)
++                traceInterpreter("| invocationCounter", ctr);
++            if (ctr >= COMPILE_THRESHOLD) {
+                 // Replace vmentry with a bytecode version of this LF.
+-                compileToBytecode();
++                compileToBytecode(false);
+             }
+         }
+     }
+     Object interpretWithArgumentsTracing(Object... argumentValues) throws Throwable {
+         traceInterpreter("[ interpretWithArguments", this, argumentValues);
+-        if (invocationCounter < COMPILE_THRESHOLD) {
+-            int ctr = invocationCounter++;  // benign race
+-            traceInterpreter("| invocationCounter", ctr);
+-            if (invocationCounter >= COMPILE_THRESHOLD) {
+-                compileToBytecode();
+-            }
+-        }
++        checkInvocationCounter();
+         Object rval;
+         try {
+             assert(arityCheck(argumentValues));
+@@ -707,7 +597,14 @@
+     }
+ 
+     public String toString() {
+-        StringBuilder buf = new StringBuilder(debugName+"=Lambda(");
++        return toString((Template)null);
++    }
++    String toString(Template temp) {
++        StringBuilder buf = new StringBuilder();
++        if (temp != null)
++            buf.append("Template(");
++        else
++            buf.append(debugName).append("=Lambda(");
+         for (int i = 0; i < names.length; i++) {
+             if (i == arity)  buf.append(")=>{");
+             Name n = names[i];
+@@ -717,7 +614,7 @@
+                 if (i+1 < arity)  buf.append(",");
+                 continue;
+             }
+-            buf.append("=").append(n.exprString());
++            buf.append("=").append(n.exprString(temp));
+             buf.append(";");
+         }
+         buf.append(result < 0 ? "void" : names[result]).append("}");
+@@ -968,6 +865,193 @@
+         return true;
+     }
+ 
++    /** Use this for making equivalence classes of LFs. */
++    static class Template {
++        final LambdaForm form;
++        Template(LambdaForm form) {
++            this.form = form;
++        }
++        @Override public int hashCode() {
++            final int M1 = 31;
++            int hc = form.arity * M1 + form.result;
++            for (Name name : form.names) {
++                hc *= M1;
++                hc += nameHashCode(name);
++            }
++            return hc;
++        }
++        boolean sameFormAfterFactoring(LambdaForm form1, LambdaForm form2) {
++            if (form1.arity  != form2.arity)   return false;
++            if (form1.result != form2.result)  return false;
++            Name[] names1 = form1.names;
++            Name[] names2 = form2.names;
++            int length = names1.length;
++            if (length != names2.length)  return false;
++            for (int i = 0; i < length; i++) {
++                if (!sameNameAfterFactoring(names1[i], names2[i]))  return false;
++                assert(nameHashCode(names1[i]) == nameHashCode(names2[i]));
++            }
++            return true;
++        }
++
++        int nameHashCode(Name name) {
++            final int M2 = 127;
++            int nhc = name.type;
++            if (name.isParam())
++                return nhc;
++            nhc *= M2;
++            if (shouldBeFactored(name, -1))
++                nhc += name.function.basicTypeSignature().hashCode();
++            else
++                nhc += name.function.hashCode();
++            int j = -1;
++            for (Object a : name.arguments) {
++                ++j;  // index of name
++                int ahc;
++                if (a instanceof Name)
++                    ahc = ((Name)a).indexAndType();
++                else if (shouldBeFactored(name, j))
++                    ahc = 0;  // this value will be factored; suppress
++                else
++                    ahc = a.hashCode();
++                nhc = (nhc * M2) + ahc;
++            }
++            return nhc;
++        }
++
++        boolean sameNameAfterFactoring(Name n1, Name n2) {
++            if (n1 == n2)  return true;
++            if (n1.indexAndType() != n2.indexAndType())  return false;
++            if (n1.isParam())  return n2.isParam();
++            if (n2.isParam())  return false;
++            int argc = n1.arguments.length;
++            if (argc != n2.arguments.length)  return false;
++            String sig = n1.function.basicTypeSignature();
++            if (!sig.equals(n2.function.basicTypeSignature()))  return false;
++            if (!shouldBeFactored(n1, -1)) {
++                // check identity of functions
++                if (!n1.function.equals(n2.function))  return false;
++            }
++            assert(shouldBeFactored(n1, -1) == shouldBeFactored(n2, -1));
++            for (int j = 0; j < argc; j++) {
++                Object a1 = n1.arguments[j];
++                Object a2 = n2.arguments[j];
++                if (a1 == a2)  continue;
++                if (a1 instanceof Name) {
++                    if (a2 instanceof Name && ((Name)a1).index == ((Name)a2).index)
++                        continue;
++                    return false;
++                }
++                if (a2 instanceof Name)  return false;
++                if (!shouldBeFactored(n1, j)) {
++                    // recheck identity of arguments
++                    if (sig.charAt(j) != 'L') {
++                        if (a1.equals(a2))  a1 = a2;  // rebox
++                    }
++                    if (a1 != a2)  return false;
++                }
++                assert(shouldBeFactored(n1, j) == shouldBeFactored(n2, j));
++            }
++            return true;
++        }
++
++        @Override public boolean equals(Object x) {
++            return x instanceof Template && equals((Template)x);
++        }
++        public boolean equals(Template that) {
++            if (this == that || this.form == that.form)  return true;
++            boolean z = sameFormAfterFactoring(this.form, that.form);
++            assert(!(z && this.hashCode() != that.hashCode()));
++            return z;
++        }
++
++        boolean shouldBeFactored(Name name, int argIndex) {
++            int BSL = (BYTECODE_SHARE_LEVEL != null ? (int)BYTECODE_SHARE_LEVEL : 255);
++            if (BSL == 0) {
++                return false;
++            }
++            if (argIndex < 0) {
++                // Factor the head of the function application?
++                if ((BSL & 1) == 0)  return false;
++                if (name.function.isFactored())  return true;  // already decided
++                MethodType mt = name.function.methodType();
++                if (mt.parameterSlotCount() >= 255)  return false;  // edge case: no invokeBasic
++                MemberName m = name.function.member;
++                if (m == null)  return true;
++                // Factor if the declaring class is not on the BCP.
++                // See also InvokerBytecodeGenerator.isStaticallyInvocable.
++                return m.getDeclaringClass().getClassLoader() != null;
++            }
++            Object arg = name.arguments[argIndex];
++            if (arg == null)
++                return false;
++            if (arg instanceof Name)
++                return false;
++            if ((BSL & 2) == 0) {
++                Class<?> ptype = name.function.methodType().parameterType(argIndex);
++                if (ptype.isPrimitive() || ptype == String.class) {
++                    return false;
++                }
++            }
++            return true;
++        }
++
++        /** Generate a fully factored lambda form. */
++        Template copyFactored() {
++            Name[] names = form.names.clone();
++            boolean changed = false;
++            int startFixing = -1;
++            for (int i = form.arity; i < names.length; i++) {
++                Name n0 = names[i];
++                Name n1 = copyFactored(n0);
++                if (changed)
++                    // also edit occurrences of previously changed names
++                    n1 = n1.replaceNames(form.names, names, startFixing, i);
++                if (n1 == n0)  continue;
++                names[i] = n1;
++                if (!changed)  startFixing = i;
++                changed = true;
++            }
++            if (!changed)  return this;
++            LambdaForm lfact = new LambdaForm(form.debugName, form.arity, names, form.result);
++            Template tfact = new Template(lfact);
++            assert(tfact.equals(this)) : Arrays.asList(tfact, this, form);
++            return tfact;
++        }
++        private Name copyFactored(Name name) {
++            boolean changed = false;
++            NamedFunction f = name.function;
++            if (shouldBeFactored(name, -1)) {
++                f = f.copyFactored();
++                changed = true;
++            }
++            Object[] args = name.arguments.clone();
++            String sig = name.function.basicTypeSignature();
++            for (int j = 0; j < args.length; j++) {
++                if (!shouldBeFactored(name, j))  continue;
++                args[j] = copyFactoredArgument(sig.charAt(j), args[j]);
++                changed = true;
++            }
++            if (!changed)  return name;
++            return new Name(f, args);
++        }
++        private Object copyFactoredArgument(char type, Object arg) {
++            if (type == 'L')  return "**";
++            return Wrapper.forBasicType(type).zero();
++        }
++
++        @Override public String toString() {
++            return form.toString(this);
++        }
++
++    }
++
++    /** Get the function for the Nth name.  This is used by the bytecode compiler and can be constant folded. */
++    MethodHandle getResolvedHandleAt(int index) {
++        return names[index].function.resolvedHandle();
++    }
++
++    /** The head of a function application in a Name. */
+     static class NamedFunction {
+         final MemberName member;
+         @Stable MethodHandle resolvedHandle;
+@@ -1002,24 +1086,39 @@
+             return resolvedHandle;
+         }
+ 
++        NamedFunction copyFactored() {
++            MemberName fake = new MemberName(Void.class, "***", methodType().basicType(), REF_invokeStatic);
++            return new NamedFunction(fake);
++        }
++        boolean isFactored() {
++            return member != null && member.getDeclaringClass() == Void.class && member.getName().equals("***");
++        }
++
+         void resolve() {
+             resolvedHandle = DirectMethodHandle.make(member);
+         }
+ 
+         @Override
+-        public boolean equals(Object other) {
+-            if (this == other) return true;
+-            if (other == null) return false;
+-            if (!(other instanceof NamedFunction)) return false;
+-            NamedFunction that = (NamedFunction) other;
+-            return this.member != null && this.member.equals(that.member);
++        public boolean equals(Object x) {
++            return x instanceof NamedFunction && equals((NamedFunction)x);
++        }
++        public boolean equals(NamedFunction that) {
++            if (this == that) return true;
++            if (that == null) return false;
++            if (this.member != null)
++                return this.member.equals(that.member);
++            assert(this.resolvedHandle != null);
++            return (that.member == null &&
++                    this.resolvedHandle != null &&
++                    this.resolvedHandle == that.resolvedHandle);
+         }
+ 
+         @Override
+         public int hashCode() {
+             if (member != null)
+                 return member.hashCode();
+-            return super.hashCode();
++            else
++                return resolvedHandle.hashCode();
+         }
+ 
+         // Put the predefined NamedFunction invokers into the table.
+@@ -1251,15 +1350,9 @@
+         return btypes;
+     }
+     public static String basicTypeSignature(MethodType type) {
+-        char[] sig = new char[type.parameterCount() + 2];
+-        int sigp = 0;
+-        for (Class<?> pt : type.parameterList()) {
+-            sig[sigp++] = basicType(pt);
+-        }
+-        sig[sigp++] = '_';
+-        sig[sigp++] = basicType(type.returnType());
+-        assert(sigp == sig.length);
+-        return String.valueOf(sig);
++        String sig = type.basicType().form().typeString();
++        assert(sig.matches("[LIJFD]*_[LIJFDV]"));
++        return sig;
+     }
+ 
+     static final class Name {
+@@ -1392,13 +1485,26 @@
+             return (function == null) ? s : s + "=" + exprString();
+         }
+         public String exprString() {
++            return exprString((Template)null);
++        }
++        public String exprString(Template temp) {
+             if (function == null)  return "null";
+-            StringBuilder buf = new StringBuilder(function.toString());
++            StringBuilder buf = new StringBuilder();
++            if (temp != null && temp.shouldBeFactored(this, -1))
++                buf.append("***").append(function.basicTypeSignature());
++            else
++                buf.append(function.toString());
+             buf.append("(");
+             String cma = "";
++            int j = -1;
+             for (Object a : arguments) {
+                 buf.append(cma); cma = ",";
+-                if (a instanceof Name || a instanceof Integer)
++                ++j;
++                if (a instanceof Name)
++                    buf.append(a);
++                else if (temp != null && temp.shouldBeFactored(this, j))
++                    buf.append("***");
++                else if (a instanceof Integer)
+                     buf.append(a);
+                 else
+                     buf.append("(").append(a).append(")");
+@@ -1454,7 +1560,7 @@
+         public boolean equals(Name that) {
+             if (this == that)  return true;
+             if (isParam())
+-                // each parameter is a unique atom
++            // each parameter is a unique atom
+                 return false;  // this != that
+             return
+                 //this.index == that.index &&
+@@ -1466,10 +1572,13 @@
+         public boolean equals(Object x) {
+             return x instanceof Name && equals((Name)x);
+         }
++        int indexAndType() {
++            return (index + (type << 8));
++        }
+         @Override
+         public int hashCode() {
+             if (isParam())
+-                return index | (type << 8);
++                return indexAndType();
+             return function.hashCode() ^ Arrays.hashCode(arguments);
+         }
+     }
+@@ -1565,12 +1674,6 @@
+     private static double zeroD() { return 0; }
+     private static Object zeroL() { return null; }
+ 
+-    // Put this last, so that previous static inits can run before.
+-    static {
+-        if (USE_PREDEFINED_INTERPRET_METHODS)
+-            PREPARED_FORMS.putAll(computeInitialPreparedForms());
+-    }
+-
+     /**
+      * Internal marker for byte-compiled LambdaForms.
+      */
+diff --git a/src/share/classes/java/lang/invoke/MemberName.java b/src/share/classes/java/lang/invoke/MemberName.java
+--- a/src/share/classes/java/lang/invoke/MemberName.java
++++ b/src/share/classes/java/lang/invoke/MemberName.java
+@@ -151,7 +151,10 @@
+         MethodType itype = getMethodOrFieldType();
+         if (isConstructor() && getReferenceKind() == REF_newInvokeSpecial)
+             return itype.changeReturnType(clazz);
+-        if (!isStatic())
++        //if (!isStatic())
++        assert(!isStatic() == MethodHandleNatives.refKindHasReceiver(getReferenceKind())
++                || !isResolved());
++        if (MethodHandleNatives.refKindHasReceiver(getReferenceKind()))
+             return itype.insertParameterTypes(0, clazz);
+         return itype;
+     }
+diff --git a/src/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/share/classes/java/lang/invoke/MethodHandleImpl.java
+--- a/src/share/classes/java/lang/invoke/MethodHandleImpl.java
++++ b/src/share/classes/java/lang/invoke/MethodHandleImpl.java
+@@ -424,7 +424,9 @@
+             Class<?> src = lambdaType.parameterType(i);
+             if (i == spreadArgPos) {
+                 // Spread the array.
+-                MethodHandle aload = MethodHandles.arrayElementGetter(spreadArgType);
++                Class<?> basicArrayType = spreadArgType;
++                if (Object[].class.isAssignableFrom(spreadArgType))  basicArrayType = Object[].class;
++                MethodHandle aload = ArrayAccessor.getAccessor(basicArrayType, false);
+                 Name array = names[argIndex];
+                 names[nameCursor++] = new Name(NF_checkSpreadArgument, array, spreadArgCount);
+                 for (int j = 0; j < spreadArgCount; i++, j++) {
+diff --git a/src/share/classes/java/lang/invoke/MethodHandleStatics.java b/src/share/classes/java/lang/invoke/MethodHandleStatics.java
+--- a/src/share/classes/java/lang/invoke/MethodHandleStatics.java
++++ b/src/share/classes/java/lang/invoke/MethodHandleStatics.java
+@@ -46,8 +46,9 @@
+     static final boolean TRACE_INTERPRETER;
+     static final boolean TRACE_METHOD_LINKAGE;
+     static final Integer COMPILE_THRESHOLD;
++    static final Integer BYTECODE_SHARE_LEVEL;
+     static {
+-        final Object[] values = { false, false, false, false, null };
++        final Object[] values = { false, false, false, false, null, null };
+         AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                 public Void run() {
+                     values[0] = Boolean.getBoolean("java.lang.invoke.MethodHandle.DEBUG_NAMES");
+@@ -55,6 +56,7 @@
+                     values[2] = Boolean.getBoolean("java.lang.invoke.MethodHandle.TRACE_INTERPRETER");
+                     values[3] = Boolean.getBoolean("java.lang.invoke.MethodHandle.TRACE_METHOD_LINKAGE");
+                     values[4] = Integer.getInteger("java.lang.invoke.MethodHandle.COMPILE_THRESHOLD");
++                    values[5] = Integer.getInteger("java.lang.invoke.MethodHandle.BYTECODE_SHARE_LEVEL");
+                     return null;
+                 }
+             });
+@@ -63,6 +65,7 @@
+         TRACE_INTERPRETER         = (Boolean) values[2];
+         TRACE_METHOD_LINKAGE      = (Boolean) values[3];
+         COMPILE_THRESHOLD         = (Integer) values[4];
++        BYTECODE_SHARE_LEVEL      = (Integer) values[5];
+     }
+ 
+     /*non-public*/ static String getNameString(MethodHandle target, MethodType type) {
+diff --git a/src/share/classes/java/lang/invoke/MethodTypeForm.java b/src/share/classes/java/lang/invoke/MethodTypeForm.java
+--- a/src/share/classes/java/lang/invoke/MethodTypeForm.java
++++ b/src/share/classes/java/lang/invoke/MethodTypeForm.java
+@@ -85,6 +85,23 @@
+         return basicType;
+     }
+ 
++    public String typeString() {
++        if (typeString == null) {
++            MethodType type = erasedType;
++            char[] sig = new char[type.parameterCount() + 2];
++            int sigp = 0;
++            for (Class<?> pt : type.parameterList()) {
++                sig[sigp++] = Wrapper.basicTypeChar(pt);
++            }
++            sig[sigp++] = '_';
++            Class<?> rt = type.returnType();
++            sig[sigp++] = Wrapper.basicTypeChar(rt);
++            assert(sigp == sig.length);
++            return typeString = String.valueOf(sig);
++        }
++        return typeString;
++    }
++
+     public LambdaForm cachedLambdaForm(int which) {
+         return lambdaForms[which];
+     }
--- a/series	Mon Oct 15 17:48:49 2012 -0700
+++ b/series	Tue Oct 16 00:18:56 2012 -0700
@@ -7,7 +7,7 @@
 
 # non-pushed files are under review or development, or merely experimental:
 anno-stable.patch               #-/meth #+1dde94130b0c
-meth-lfi.patch                  #-/meth #+1dde94130b0c #-testable
+meth-lfi.patch                  #-/meth #+1dde94130b0c
 meth-info-7087570.patch         #-/meth #+1dde94130b0c
 meth.patch                      #-/meth #+1dde94130b0c
 meth-7177472.patch              #-/meth #+1dde94130b0c #-buildable