8027827: Improve performance of catchException combinator
8034120: MethodHandles.catchException doesn't handle VarargsCollector right

Reviewed-by: lagergren, psandoz, jrose
diff --git a/jdk/src/share/classes/java/lang/invoke/BoundMethodHandle.java b/jdk/src/share/classes/java/lang/invoke/BoundMethodHandle.java
index a6bb5ec..434f3e3 100644
--- a/jdk/src/share/classes/java/lang/invoke/BoundMethodHandle.java
+++ b/jdk/src/share/classes/java/lang/invoke/BoundMethodHandle.java
@@ -861,4 +861,18 @@
      * All subclasses must provide such a value describing their type signature.
      */
     static final SpeciesData SPECIES_DATA = SpeciesData.EMPTY;
+
+    private static final SpeciesData[] SPECIES_DATA_CACHE = new SpeciesData[5];
+    private static SpeciesData checkCache(int size, String types) {
+        int idx = size - 1;
+        SpeciesData data = SPECIES_DATA_CACHE[idx];
+        if (data != null)  return data;
+        SPECIES_DATA_CACHE[idx] = data = getSpeciesData(types);
+        return data;
+    }
+    static SpeciesData speciesData_L()     { return checkCache(1, "L"); }
+    static SpeciesData speciesData_LL()    { return checkCache(2, "LL"); }
+    static SpeciesData speciesData_LLL()   { return checkCache(3, "LLL"); }
+    static SpeciesData speciesData_LLLL()  { return checkCache(4, "LLLL"); }
+    static SpeciesData speciesData_LLLLL() { return checkCache(5, "LLLLL"); }
 }
diff --git a/jdk/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java b/jdk/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
index 6a79291..d31b6f4 100644
--- a/jdk/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
+++ b/jdk/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;
 
@@ -39,8 +38,6 @@
 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 sun.invoke.util.VerifyType;
 
 /**
@@ -51,7 +48,6 @@
 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";
@@ -511,17 +507,22 @@
             Name name = lambdaForm.names[i];
             MemberName member = name.function.member();
 
-            if (isSelectAlternative(member)) {
-                // selectAlternative idiom
-                // FIXME: make sure this idiom is really present!
+            if (isSelectAlternative(i)) {
                 emitSelectAlternative(name, lambdaForm.names[i + 1]);
                 i++;  // skip MH.invokeBasic of the selectAlternative result
+            } else if (isGuardWithCatch(i)) {
+                emitGuardWithCatch(i);
+                i = i+2; // Jump to the end of GWC idiom
             } else if (isStaticallyInvocable(member)) {
                 emitStaticInvoke(member, name);
             } else {
                 emitInvoke(name);
             }
 
+            // Update cached form name's info in case an intrinsic spanning multiple names was encountered.
+            name = lambdaForm.names[i];
+            member = name.function.member();
+
             // store the result from evaluating to the target name in a local if required
             // (if this is the last value, i.e., the one that is going to be returned,
             // avoid store/load/return and just return)
@@ -674,12 +675,66 @@
     }
 
     /**
-     * Check if MemberName is a call to MethodHandleImpl.selectAlternative.
+     * Check if MemberName is a call to a method named {@code name} in class {@code declaredClass}.
      */
-    private boolean isSelectAlternative(MemberName member) {
+    private boolean memberRefersTo(MemberName member, Class<?> declaringClass, String name) {
         return member != null &&
-               member.getDeclaringClass() == MethodHandleImpl.class &&
-               member.getName().equals("selectAlternative");
+               member.getDeclaringClass() == declaringClass &&
+               member.getName().equals(name);
+    }
+    private boolean nameRefersTo(Name name, Class<?> declaringClass, String methodName) {
+        return name.function != null &&
+               memberRefersTo(name.function.member(), declaringClass, methodName);
+    }
+
+    /**
+     * Check if MemberName is a call to MethodHandle.invokeBasic.
+     */
+    private boolean isInvokeBasic(Name name) {
+        if (name.function == null)
+            return false;
+        if (name.arguments.length < 1)
+            return false;  // must have MH argument
+        MemberName member = name.function.member();
+        return memberRefersTo(member, MethodHandle.class, "invokeBasic") &&
+               !member.isPublic() && !member.isStatic();
+    }
+
+    /**
+     * Check if i-th name is a call to MethodHandleImpl.selectAlternative.
+     */
+    private boolean isSelectAlternative(int pos) {
+        // selectAlternative idiom:
+        //   t_{n}:L=MethodHandleImpl.selectAlternative(...)
+        //   t_{n+1}:?=MethodHandle.invokeBasic(t_{n}, ...)
+        if (pos+1 >= lambdaForm.names.length)  return false;
+        Name name0 = lambdaForm.names[pos];
+        Name name1 = lambdaForm.names[pos+1];
+        return nameRefersTo(name0, MethodHandleImpl.class, "selectAlternative") &&
+               isInvokeBasic(name1) &&
+               name1.lastUseIndex(name0) == 0 &&        // t_{n+1}:?=MethodHandle.invokeBasic(t_{n}, ...)
+               lambdaForm.lastUseIndex(name0) == pos+1; // t_{n} is local: used only in t_{n+1}
+    }
+
+    /**
+     * Check if i-th name is a start of GuardWithCatch idiom.
+     */
+    private boolean isGuardWithCatch(int pos) {
+        // GuardWithCatch idiom:
+        //   t_{n}:L=MethodHandle.invokeBasic(...)
+        //   t_{n+1}:L=MethodHandleImpl.guardWithCatch(*, *, *, t_{n});
+        //   t_{n+2}:?=MethodHandle.invokeBasic(t_{n+1})
+        if (pos+2 >= lambdaForm.names.length)  return false;
+        Name name0 = lambdaForm.names[pos];
+        Name name1 = lambdaForm.names[pos+1];
+        Name name2 = lambdaForm.names[pos+2];
+        return nameRefersTo(name1, MethodHandleImpl.class, "guardWithCatch") &&
+               isInvokeBasic(name0) &&
+               isInvokeBasic(name2) &&
+               name1.lastUseIndex(name0) == 3 &&          // t_{n+1}:L=MethodHandleImpl.guardWithCatch(*, *, *, t_{n});
+               lambdaForm.lastUseIndex(name0) == pos+1 && // t_{n} is local: used only in t_{n+1}
+               name2.lastUseIndex(name1) == 1 &&          // t_{n+2}:?=MethodHandle.invokeBasic(t_{n+1})
+               lambdaForm.lastUseIndex(name1) == pos+2;   // t_{n+1} is local: used only in t_{n+2}
     }
 
     /**
@@ -694,8 +749,6 @@
      * }</pre></blockquote>
      */
     private void emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) {
-        MethodType type = selectAlternativeName.function.methodType();
-
         Name receiver = (Name) invokeBasicName.arguments[0];
 
         Label L_fallback = new Label();
@@ -709,7 +762,6 @@
         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);
@@ -721,7 +773,6 @@
         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);
@@ -730,6 +781,85 @@
         mv.visitLabel(L_done);
     }
 
+    /**
+      * Emit bytecode for the guardWithCatch idiom.
+      *
+      * The pattern looks like (Cf. MethodHandleImpl.makeGuardWithCatch):
+      * <blockquote><pre>{@code
+      *  guardWithCatch=Lambda(a0:L,a1:L,a2:L,a3:L,a4:L,a5:L,a6:L,a7:L)=>{
+      *    t8:L=MethodHandle.invokeBasic(a4:L,a6:L,a7:L);
+      *    t9:L=MethodHandleImpl.guardWithCatch(a1:L,a2:L,a3:L,t8:L);
+      *   t10:I=MethodHandle.invokeBasic(a5:L,t9:L);t10:I}
+      * }</pre></blockquote>
+      *
+      * It is compiled into bytecode equivalent of the following code:
+      * <blockquote><pre>{@code
+      *  try {
+      *      return a1.invokeBasic(a6, a7);
+      *  } catch (Throwable e) {
+      *      if (!a2.isInstance(e)) throw e;
+      *      return a3.invokeBasic(ex, a6, a7);
+      *  }}
+      */
+    private void emitGuardWithCatch(int pos) {
+        Name args    = lambdaForm.names[pos];
+        Name invoker = lambdaForm.names[pos+1];
+        Name result  = lambdaForm.names[pos+2];
+
+        Label L_startBlock = new Label();
+        Label L_endBlock = new Label();
+        Label L_handler = new Label();
+        Label L_done = new Label();
+
+        Class<?> returnType = result.function.resolvedHandle.type().returnType();
+        MethodType type = args.function.resolvedHandle.type()
+                              .dropParameterTypes(0,1)
+                              .changeReturnType(returnType);
+
+        mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_handler, "java/lang/Throwable");
+
+        // Normal case
+        mv.visitLabel(L_startBlock);
+        // load target
+        emitPushArgument(invoker, 0);
+        emitPushArguments(args, 1); // skip 1st argument: method handle
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.basicType().toMethodDescriptorString(), false);
+        mv.visitLabel(L_endBlock);
+        mv.visitJumpInsn(Opcodes.GOTO, L_done);
+
+        // Exceptional case
+        mv.visitLabel(L_handler);
+
+        // Check exception's type
+        mv.visitInsn(Opcodes.DUP);
+        // load exception class
+        emitPushArgument(invoker, 1);
+        mv.visitInsn(Opcodes.SWAP);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "isInstance", "(Ljava/lang/Object;)Z", false);
+        Label L_rethrow = new Label();
+        mv.visitJumpInsn(Opcodes.IFEQ, L_rethrow);
+
+        // Invoke catcher
+        // load catcher
+        emitPushArgument(invoker, 2);
+        mv.visitInsn(Opcodes.SWAP);
+        emitPushArguments(args, 1); // skip 1st argument: method handle
+        MethodType catcherType = type.insertParameterTypes(0, Throwable.class);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", catcherType.basicType().toMethodDescriptorString(), false);
+        mv.visitJumpInsn(Opcodes.GOTO, L_done);
+
+        mv.visitLabel(L_rethrow);
+        mv.visitInsn(Opcodes.ATHROW);
+
+        mv.visitLabel(L_done);
+    }
+
+    private void emitPushArguments(Name args, int start) {
+        for (int i = start; i < args.arguments.length; i++) {
+            emitPushArgument(args, i);
+        }
+    }
+
     private void emitPushArgument(Name name, int paramIndex) {
         Object arg = name.arguments[paramIndex];
         char ptype = name.function.parameterType(paramIndex);
diff --git a/jdk/src/share/classes/java/lang/invoke/LambdaForm.java b/jdk/src/share/classes/java/lang/invoke/LambdaForm.java
index c3ab19f..a5e40ed 100644
--- a/jdk/src/share/classes/java/lang/invoke/LambdaForm.java
+++ b/jdk/src/share/classes/java/lang/invoke/LambdaForm.java
@@ -1465,6 +1465,33 @@
             return false;
         }
 
+        /** Return the index of the last occurrence of n in the argument array.
+         *  Return -1 if the name is not used.
+         */
+        int lastUseIndex(Name n) {
+            if (arguments == null)  return -1;
+            for (int i = arguments.length; --i >= 0; ) {
+                if (arguments[i] == n)  return i;
+            }
+            return -1;
+        }
+
+        /** Return the number of occurrences of n in the argument array.
+         *  Return 0 if the name is not used.
+         */
+        int useCount(Name n) {
+            if (arguments == null)  return 0;
+            int count = 0;
+            for (int i = arguments.length; --i >= 0; ) {
+                if (arguments[i] == n)  ++count;
+            }
+            return count;
+        }
+
+        boolean contains(Name n) {
+            return this == n || lastUseIndex(n) >= 0;
+        }
+
         public boolean equals(Name that) {
             if (this == that)  return true;
             if (isParam())
@@ -1488,6 +1515,35 @@
         }
     }
 
+    /** Return the index of the last name which contains n as an argument.
+     *  Return -1 if the name is not used.  Return names.length if it is the return value.
+     */
+    int lastUseIndex(Name n) {
+        int ni = n.index, nmax = names.length;
+        assert(names[ni] == n);
+        if (result == ni)  return nmax;  // live all the way beyond the end
+        for (int i = nmax; --i > ni; ) {
+            if (names[i].lastUseIndex(n) >= 0)
+                return i;
+        }
+        return -1;
+    }
+
+    /** Return the number of times n is used as an argument or return value. */
+    int useCount(Name n) {
+        int ni = n.index, nmax = names.length;
+        int end = lastUseIndex(n);
+        if (end < 0)  return 0;
+        int count = 0;
+        if (end == nmax) { count++; end--; }
+        int beg = n.index() + 1;
+        if (beg < arity)  beg = arity;
+        for (int i = beg; i <= end; i++) {
+            count += names[i].useCount(n);
+        }
+        return count;
+    }
+
     static Name argument(int which, char type) {
         int tn = ALL_TYPES.indexOf(type);
         if (tn < 0 || which >= INTERNED_ARGUMENT_LIMIT)
diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java b/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java
index eca236c..cd7dec4 100644
--- a/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java
+++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java
@@ -27,7 +27,6 @@
 
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import sun.invoke.empty.Empty;
@@ -482,12 +481,26 @@
      * Factored in an inner class to delay initialization until first usage.
      */
     private static class Lazy {
+        private static final Class<?> MHI = MethodHandleImpl.class;
+
         static final NamedFunction NF_checkSpreadArgument;
+        static final NamedFunction NF_guardWithCatch;
+        static final NamedFunction NF_selectAlternative;
+        static final NamedFunction NF_throwException;
+
         static {
             try {
-                NF_checkSpreadArgument = new NamedFunction(MethodHandleImpl.class
-                        .getDeclaredMethod("checkSpreadArgument", Object.class, int.class));
+                NF_checkSpreadArgument = new NamedFunction(MHI.getDeclaredMethod("checkSpreadArgument", Object.class, int.class));
+                NF_guardWithCatch      = new NamedFunction(MHI.getDeclaredMethod("guardWithCatch", MethodHandle.class, Class.class,
+                                                                                 MethodHandle.class, Object[].class));
+                NF_selectAlternative   = new NamedFunction(MHI.getDeclaredMethod("selectAlternative", boolean.class, MethodHandle.class,
+                                                                                 MethodHandle.class));
+                NF_throwException      = new NamedFunction(MHI.getDeclaredMethod("throwException", Throwable.class));
+
                 NF_checkSpreadArgument.resolve();
+                NF_guardWithCatch.resolve();
+                NF_selectAlternative.resolve();
+                NF_throwException.resolve();
             } catch (ReflectiveOperationException ex) {
                 throw newInternalError(ex);
             }
@@ -548,24 +561,12 @@
         return SimpleMethodHandle.make(srcType, form);
     }
 
+    @LambdaForm.Hidden
     static
     MethodHandle selectAlternative(boolean testResult, MethodHandle target, MethodHandle fallback) {
         return testResult ? target : fallback;
     }
 
-    static MethodHandle SELECT_ALTERNATIVE;
-    static MethodHandle selectAlternative() {
-        if (SELECT_ALTERNATIVE != null)  return SELECT_ALTERNATIVE;
-        try {
-            SELECT_ALTERNATIVE
-            = IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "selectAlternative",
-                    MethodType.methodType(MethodHandle.class, boolean.class, MethodHandle.class, MethodHandle.class));
-        } catch (ReflectiveOperationException ex) {
-            throw new RuntimeException(ex);
-        }
-        return SELECT_ALTERNATIVE;
-    }
-
     static
     MethodHandle makeGuardWithTest(MethodHandle test,
                                    MethodHandle target,
@@ -585,7 +586,7 @@
 
         // call selectAlternative
         Object[] selectArgs = { names[arity + 1], target, fallback };
-        names[arity + 2] = new Name(MethodHandleImpl.selectAlternative(), selectArgs);
+        names[arity + 2] = new Name(Lazy.NF_selectAlternative, selectArgs);
         targetArgs[0] = names[arity + 2];
 
         // call target or fallback
@@ -595,167 +596,137 @@
         return SimpleMethodHandle.make(target.type(), form);
     }
 
-    private static class GuardWithCatch {
-        private final MethodHandle target;
-        private final Class<? extends Throwable> exType;
-        private final MethodHandle catcher;
-        // FIXME: Build the control flow out of foldArguments.
-        GuardWithCatch(MethodHandle target, Class<? extends Throwable> exType, MethodHandle catcher) {
-            this.target = target;
-            this.exType = exType;
-            this.catcher = catcher;
-        }
-        @LambdaForm.Hidden
-        private Object invoke_V(Object... av) throws Throwable {
-            try {
-                return target.invokeExact(av);
-            } catch (Throwable t) {
-                if (!exType.isInstance(t))  throw t;
-                return catcher.invokeExact(t, av);
-            }
-        }
-        @LambdaForm.Hidden
-        private Object invoke_L0() throws Throwable {
-            try {
-                return target.invokeExact();
-            } catch (Throwable t) {
-                if (!exType.isInstance(t))  throw t;
-                return catcher.invokeExact(t);
-            }
-        }
-        @LambdaForm.Hidden
-        private Object invoke_L1(Object a0) throws Throwable {
-            try {
-                return target.invokeExact(a0);
-            } catch (Throwable t) {
-                if (!exType.isInstance(t))  throw t;
-                return catcher.invokeExact(t, a0);
-            }
-        }
-        @LambdaForm.Hidden
-        private Object invoke_L2(Object a0, Object a1) throws Throwable {
-            try {
-                return target.invokeExact(a0, a1);
-            } catch (Throwable t) {
-                if (!exType.isInstance(t))  throw t;
-                return catcher.invokeExact(t, a0, a1);
-            }
-        }
-        @LambdaForm.Hidden
-        private Object invoke_L3(Object a0, Object a1, Object a2) throws Throwable {
-            try {
-                return target.invokeExact(a0, a1, a2);
-            } catch (Throwable t) {
-                if (!exType.isInstance(t))  throw t;
-                return catcher.invokeExact(t, a0, a1, a2);
-            }
-        }
-        @LambdaForm.Hidden
-        private Object invoke_L4(Object a0, Object a1, Object a2, Object a3) throws Throwable {
-            try {
-                return target.invokeExact(a0, a1, a2, a3);
-            } catch (Throwable t) {
-                if (!exType.isInstance(t))  throw t;
-                return catcher.invokeExact(t, a0, a1, a2, a3);
-            }
-        }
-        @LambdaForm.Hidden
-        private Object invoke_L5(Object a0, Object a1, Object a2, Object a3, Object a4) throws Throwable {
-            try {
-                return target.invokeExact(a0, a1, a2, a3, a4);
-            } catch (Throwable t) {
-                if (!exType.isInstance(t))  throw t;
-                return catcher.invokeExact(t, a0, a1, a2, a3, a4);
-            }
-        }
-        @LambdaForm.Hidden
-        private Object invoke_L6(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5) throws Throwable {
-            try {
-                return target.invokeExact(a0, a1, a2, a3, a4, a5);
-            } catch (Throwable t) {
-                if (!exType.isInstance(t))  throw t;
-                return catcher.invokeExact(t, a0, a1, a2, a3, a4, a5);
-            }
-        }
-        @LambdaForm.Hidden
-        private Object invoke_L7(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6) throws Throwable {
-            try {
-                return target.invokeExact(a0, a1, a2, a3, a4, a5, a6);
-            } catch (Throwable t) {
-                if (!exType.isInstance(t))  throw t;
-                return catcher.invokeExact(t, a0, a1, a2, a3, a4, a5, a6);
-            }
-        }
-        @LambdaForm.Hidden
-        private Object invoke_L8(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7) throws Throwable {
-            try {
-                return target.invokeExact(a0, a1, a2, a3, a4, a5, a6, a7);
-            } catch (Throwable t) {
-                if (!exType.isInstance(t))  throw t;
-                return catcher.invokeExact(t, a0, a1, a2, a3, a4, a5, a6, a7);
-            }
-        }
-        static MethodHandle[] makeInvokes() {
-            ArrayList<MethodHandle> invokes = new ArrayList<>();
-            MethodHandles.Lookup lookup = IMPL_LOOKUP;
-            for (;;) {
-                int nargs = invokes.size();
-                String name = "invoke_L"+nargs;
-                MethodHandle invoke = null;
-                try {
-                    invoke = lookup.findVirtual(GuardWithCatch.class, name, MethodType.genericMethodType(nargs));
-                } catch (ReflectiveOperationException ex) {
-                }
-                if (invoke == null)  break;
-                invokes.add(invoke);
-            }
-            assert(invokes.size() == 9);  // current number of methods
-            return invokes.toArray(new MethodHandle[0]);
-        };
-        static final MethodHandle[] INVOKES = makeInvokes();
-        // For testing use this:
-        //static final MethodHandle[] INVOKES = Arrays.copyOf(makeInvokes(), 2);
-        static final MethodHandle VARARGS_INVOKE;
-        static {
-            try {
-                VARARGS_INVOKE = IMPL_LOOKUP.findVirtual(GuardWithCatch.class, "invoke_V", MethodType.genericMethodType(0, true));
-            } catch (ReflectiveOperationException ex) {
-                throw uncaughtException(ex);
-            }
-        }
-    }
+    /**
+     * The LambaForm shape for catchException combinator is the following:
+     * <blockquote><pre>{@code
+     *  guardWithCatch=Lambda(a0:L,a1:L,a2:L)=>{
+     *    t3:L=BoundMethodHandle$Species_LLLLL.argL0(a0:L);
+     *    t4:L=BoundMethodHandle$Species_LLLLL.argL1(a0:L);
+     *    t5:L=BoundMethodHandle$Species_LLLLL.argL2(a0:L);
+     *    t6:L=BoundMethodHandle$Species_LLLLL.argL3(a0:L);
+     *    t7:L=BoundMethodHandle$Species_LLLLL.argL4(a0:L);
+     *    t8:L=MethodHandle.invokeBasic(t6:L,a1:L,a2:L);
+     *    t9:L=MethodHandleImpl.guardWithCatch(t3:L,t4:L,t5:L,t8:L);
+     *   t10:I=MethodHandle.invokeBasic(t7:L,t9:L);t10:I}
+     * }</pre></blockquote>
+     *
+     * argL0 and argL2 are target and catcher method handles. argL1 is exception class.
+     * argL3 and argL4 are auxiliary method handles: argL3 boxes arguments and wraps them into Object[]
+     * (ValueConversions.array()) and argL4 unboxes result if necessary (ValueConversions.unbox()).
+     *
+     * Having t8 and t10 passed outside and not hardcoded into a lambda form allows to share lambda forms
+     * among catchException combinators with the same basic type.
+     */
+    private static LambdaForm makeGuardWithCatchForm(MethodType basicType) {
+        MethodType lambdaType = basicType.invokerType();
 
+        LambdaForm lform = basicType.form().cachedLambdaForm(MethodTypeForm.LF_GWC);
+        if (lform != null) {
+            return lform;
+        }
+        final int THIS_MH      = 0;  // the BMH_LLLLL
+        final int ARG_BASE     = 1;  // start of incoming arguments
+        final int ARG_LIMIT    = ARG_BASE + basicType.parameterCount();
+
+        int nameCursor = ARG_LIMIT;
+        final int GET_TARGET       = nameCursor++;
+        final int GET_CLASS        = nameCursor++;
+        final int GET_CATCHER      = nameCursor++;
+        final int GET_COLLECT_ARGS = nameCursor++;
+        final int GET_UNBOX_RESULT = nameCursor++;
+        final int BOXED_ARGS       = nameCursor++;
+        final int TRY_CATCH        = nameCursor++;
+        final int UNBOX_RESULT     = nameCursor++;
+
+        Name[] names = arguments(nameCursor - ARG_LIMIT, lambdaType);
+
+        BoundMethodHandle.SpeciesData data = BoundMethodHandle.speciesData_LLLLL();
+        names[GET_TARGET]       = new Name(data.getterFunction(0), names[THIS_MH]);
+        names[GET_CLASS]        = new Name(data.getterFunction(1), names[THIS_MH]);
+        names[GET_CATCHER]      = new Name(data.getterFunction(2), names[THIS_MH]);
+        names[GET_COLLECT_ARGS] = new Name(data.getterFunction(3), names[THIS_MH]);
+        names[GET_UNBOX_RESULT] = new Name(data.getterFunction(4), names[THIS_MH]);
+
+        // FIXME: rework argument boxing/result unboxing logic for LF interpretation
+
+        // t_{i}:L=MethodHandle.invokeBasic(collectArgs:L,a1:L,...);
+        MethodType collectArgsType = basicType.changeReturnType(Object.class);
+        MethodHandle invokeBasic = MethodHandles.basicInvoker(collectArgsType);
+        Object[] args = new Object[invokeBasic.type().parameterCount()];
+        args[0] = names[GET_COLLECT_ARGS];
+        System.arraycopy(names, ARG_BASE, args, 1, ARG_LIMIT-ARG_BASE);
+        names[BOXED_ARGS] = new Name(new NamedFunction(invokeBasic), args);
+
+        // t_{i+1}:L=MethodHandleImpl.guardWithCatch(target:L,exType:L,catcher:L,t_{i}:L);
+        Object[] gwcArgs = new Object[] {names[GET_TARGET], names[GET_CLASS], names[GET_CATCHER], names[BOXED_ARGS]};
+        names[TRY_CATCH] = new Name(Lazy.NF_guardWithCatch, gwcArgs);
+
+        // t_{i+2}:I=MethodHandle.invokeBasic(unbox:L,t_{i+1}:L);
+        MethodHandle invokeBasicUnbox = MethodHandles.basicInvoker(MethodType.methodType(basicType.rtype(), Object.class));
+        Object[] unboxArgs  = new Object[] {names[GET_UNBOX_RESULT], names[TRY_CATCH]};
+        names[UNBOX_RESULT] = new Name(new NamedFunction(invokeBasicUnbox), unboxArgs);
+
+        lform = new LambdaForm("guardWithCatch", lambdaType.parameterCount(), names);
+
+        basicType.form().setCachedLambdaForm(MethodTypeForm.LF_GWC, lform);
+        return lform;
+    }
 
     static
     MethodHandle makeGuardWithCatch(MethodHandle target,
                                     Class<? extends Throwable> exType,
                                     MethodHandle catcher) {
         MethodType type = target.type();
-        MethodType ctype = catcher.type();
-        int nargs = type.parameterCount();
-        if (nargs < GuardWithCatch.INVOKES.length) {
-            MethodType gtype = type.generic();
-            MethodType gcatchType = gtype.insertParameterTypes(0, Throwable.class);
-            // Note: convertArguments(...2) avoids interface casts present in convertArguments(...0)
-            MethodHandle gtarget = makePairwiseConvert(target, gtype, 2);
-            MethodHandle gcatcher = makePairwiseConvert(catcher, gcatchType, 2);
-            GuardWithCatch gguard = new GuardWithCatch(gtarget, exType, gcatcher);
-            if (gtarget == null || gcatcher == null)  throw new InternalError();
-            MethodHandle ginvoker = GuardWithCatch.INVOKES[nargs].bindReceiver(gguard);
-            return makePairwiseConvert(ginvoker, type, 2);
+        LambdaForm form = makeGuardWithCatchForm(type.basicType());
+
+        // Prepare auxiliary method handles used during LambdaForm interpreation.
+        // Box arguments and wrap them into Object[]: ValueConversions.array().
+        MethodType varargsType = type.changeReturnType(Object[].class);
+        MethodHandle collectArgs = ValueConversions.varargsArray(type.parameterCount())
+                                                   .asType(varargsType);
+        // Result unboxing: ValueConversions.unbox() OR ValueConversions.identity() OR ValueConversions.ignore().
+        MethodHandle unboxResult;
+        if (type.returnType().isPrimitive()) {
+            unboxResult = ValueConversions.unbox(type.returnType());
         } else {
-            target = target.asType(type.changeReturnType(Object.class));
-            MethodHandle gtarget = makeSpreadArguments(target, Object[].class, 0, nargs);
-            MethodType catcherType = ctype.changeParameterType(0, Throwable.class)
-                                          .changeReturnType(Object.class);
-            catcher = catcher.asType(catcherType);
-            MethodHandle gcatcher = makeSpreadArguments(catcher, Object[].class, 1, nargs);
-            GuardWithCatch gguard = new GuardWithCatch(gtarget, exType, gcatcher);
-            if (gtarget == null || gcatcher == null)  throw new InternalError();
-            MethodHandle ginvoker = GuardWithCatch.VARARGS_INVOKE.bindReceiver(gguard);
-            MethodHandle gcollect = makeCollectArguments(ginvoker, ValueConversions.varargsArray(nargs), 0, false);
-            return makePairwiseConvert(gcollect, type, 2);
+            unboxResult = ValueConversions.identity();
         }
+
+        BoundMethodHandle.SpeciesData data = BoundMethodHandle.speciesData_LLLLL();
+        BoundMethodHandle mh;
+        try {
+            mh = (BoundMethodHandle)
+                    data.constructor[0].invokeBasic(type, form, (Object) target, (Object) exType, (Object) catcher,
+                                                    (Object) collectArgs, (Object) unboxResult);
+        } catch (Throwable ex) {
+            throw uncaughtException(ex);
+        }
+        assert(mh.type() == type);
+        return mh;
+    }
+
+    /**
+     * Intrinsified during LambdaForm compilation
+     * (see {@link InvokerBytecodeGenerator#emitGuardWithCatch emitGuardWithCatch}).
+     */
+    @LambdaForm.Hidden
+    static Object guardWithCatch(MethodHandle target, Class<? extends Throwable> exType, MethodHandle catcher,
+                                 Object... av) throws Throwable {
+        try {
+            return target.invokeWithArguments(av);
+        } catch (Throwable t) {
+            if (!exType.isInstance(t)) throw t;
+            Object[] args = prepend(t, av);
+            return catcher.invokeWithArguments(args);
+        }
+    }
+
+    /** Prepend an element {@code elem} to an {@code array}. */
+    private static Object[] prepend(Object elem, Object[] array) {
+        Object[] newArray = new Object[array.length+1];
+        newArray[0] = elem;
+        System.arraycopy(array, 0, newArray, 1, array.length);
+        return newArray;
     }
 
     static
@@ -765,23 +736,9 @@
         if (arity > 1) {
             return throwException(type.dropParameterTypes(1, arity)).dropArguments(type, 1, arity-1);
         }
-        return makePairwiseConvert(throwException(), type, 2);
+        return makePairwiseConvert(Lazy.NF_throwException.resolvedHandle(), type, 2);
     }
 
-    static MethodHandle THROW_EXCEPTION;
-    static MethodHandle throwException() {
-        MethodHandle mh = THROW_EXCEPTION;
-        if (mh != null)  return mh;
-        try {
-            mh
-            = IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "throwException",
-                    MethodType.methodType(Empty.class, Throwable.class));
-        } catch (ReflectiveOperationException ex) {
-            throw new RuntimeException(ex);
-        }
-        THROW_EXCEPTION = mh;
-        return mh;
-    }
     static <T extends Throwable> Empty throwException(T t) throws T { throw t; }
 
     static MethodHandle[] FAKE_METHOD_HANDLE_INVOKE = new MethodHandle[2];
diff --git a/jdk/src/share/classes/java/lang/invoke/MethodTypeForm.java b/jdk/src/share/classes/java/lang/invoke/MethodTypeForm.java
index 80a751c..ee5e622 100644
--- a/jdk/src/share/classes/java/lang/invoke/MethodTypeForm.java
+++ b/jdk/src/share/classes/java/lang/invoke/MethodTypeForm.java
@@ -76,7 +76,8 @@
             LF_GEN_INVOKER    = 12,
             LF_CS_LINKER      = 13,  // linkToCallSite_CS
             LF_MH_LINKER      = 14,  // linkToCallSite_MH
-            LF_LIMIT          = 15;
+            LF_GWC            = 15,
+            LF_LIMIT          = 16;
 
     public MethodType erasedType() {
         return erasedType;
diff --git a/jdk/test/java/lang/invoke/MethodHandles/TestCatchException.java b/jdk/test/java/lang/invoke/MethodHandles/TestCatchException.java
index 106eafc..8f1abfc 100644
--- a/jdk/test/java/lang/invoke/MethodHandles/TestCatchException.java
+++ b/jdk/test/java/lang/invoke/MethodHandles/TestCatchException.java
@@ -72,10 +72,45 @@
         assertEquals(x, 17);
     }
 
+
+    public static Object m1(Object o1, Object o2, Object o3, Object o4, Object o5,
+                            Object o6, Object o7, Object o8, Object... tail) {
+        return tail;
+    }
+
+    public static Object m2(Exception e, Object o1, Object o2, Object o3, Object o4,
+                            Object o5, Object o6, Object o7, Object o8, Object... tail) {
+        return tail;
+    }
+
+    @Test
+    public void testVarargsCollector() throws Throwable {
+        MethodType t1 = MethodType.methodType(Object.class, Object.class, Object.class, Object.class, Object.class,
+                Object.class, Object.class, Object.class, Object.class, Object[].class);
+
+        MethodType t2 = t1.insertParameterTypes(0, Exception.class);
+
+        MethodHandle target = LOOKUP.findStatic(TestCatchException.class, "m1", t1)
+                                    .asVarargsCollector(Object[].class);
+
+        MethodHandle catcher = LOOKUP.findStatic(TestCatchException.class, "m2", t2);
+
+        MethodHandle gwc = MethodHandles.catchException(target, Exception.class, catcher);
+
+        Object o = new Object();
+        Object[] obj1 = new Object[] { "str" };
+
+        Object r1 = target.invokeExact(o, o, o, o, o, o, o, o, obj1);
+        Object r2 = gwc.invokeExact(o, o, o, o, o, o, o, o, obj1);
+        assertEquals(r1, obj1);
+        assertEquals(r2, obj1);
+    }
+
     public static void main(String[] args) throws Throwable {
         TestCatchException test = new TestCatchException();
         test.testNoThrowPath();
         test.testThrowPath();
+        test.testVarargsCollector();
         System.out.println("TEST PASSED");
     }
 }