jdk/src/share/classes/java/lang/invoke/AdapterMethodHandle.java
changeset 9646 5ebbe5ab084f
parent 8822 8145ab9f5f86
child 9730 e4b334d47f4b
--- a/jdk/src/share/classes/java/lang/invoke/AdapterMethodHandle.java	Thu May 12 19:27:33 2011 -0700
+++ b/jdk/src/share/classes/java/lang/invoke/AdapterMethodHandle.java	Thu May 12 19:27:49 2011 -0700
@@ -27,6 +27,7 @@
 
 import sun.invoke.util.VerifyType;
 import sun.invoke.util.Wrapper;
+import sun.invoke.util.ValueConversions;
 import java.util.Arrays;
 import static java.lang.invoke.MethodHandleNatives.Constants.*;
 import static java.lang.invoke.MethodHandleStatics.*;
@@ -55,29 +56,35 @@
         this(target, newType, conv, null);
     }
 
+    int getConversion() { return conversion; }
+
     // TO DO:  When adapting another MH with a null conversion, clone
     // the target and change its type, instead of adding another layer.
 
     /** Can a JVM-level adapter directly implement the proposed
      *  argument conversions, as if by MethodHandles.convertArguments?
      */
-    static boolean canPairwiseConvert(MethodType newType, MethodType oldType) {
+    static boolean canPairwiseConvert(MethodType newType, MethodType oldType, int level) {
         // same number of args, of course
         int len = newType.parameterCount();
         if (len != oldType.parameterCount())
             return false;
 
-        // Check return type.  (Not much can be done with it.)
+        // Check return type.
         Class<?> exp = newType.returnType();
         Class<?> ret = oldType.returnType();
-        if (!VerifyType.isNullConversion(ret, exp))
-            return false;
+        if (!VerifyType.isNullConversion(ret, exp)) {
+            if (!convOpSupported(OP_COLLECT_ARGS))
+                return false;
+            if (!canConvertArgument(ret, exp, level))
+                return false;
+        }
 
         // Check args pairwise.
         for (int i = 0; i < len; i++) {
             Class<?> src = newType.parameterType(i); // source type
             Class<?> dst = oldType.parameterType(i); // destination type
-            if (!canConvertArgument(src, dst))
+            if (!canConvertArgument(src, dst, level))
                 return false;
         }
 
@@ -87,11 +94,14 @@
     /** Can a JVM-level adapter directly implement the proposed
      *  argument conversion, as if by MethodHandles.convertArguments?
      */
-    static boolean canConvertArgument(Class<?> src, Class<?> dst) {
+    static boolean canConvertArgument(Class<?> src, Class<?> dst, int level) {
         // ? Retool this logic to use RETYPE_ONLY, CHECK_CAST, etc., as opcodes,
         // so we don't need to repeat so much decision making.
         if (VerifyType.isNullConversion(src, dst)) {
             return true;
+        } else if (convOpSupported(OP_COLLECT_ARGS)) {
+            // If we can build filters, we can convert anything to anything.
+            return true;
         } else if (src.isPrimitive()) {
             if (dst.isPrimitive())
                 return canPrimCast(src, dst);
@@ -99,7 +109,7 @@
                 return canBoxArgument(src, dst);
         } else {
             if (dst.isPrimitive())
-                return canUnboxArgument(src, dst);
+                return canUnboxArgument(src, dst, level);
             else
                 return true;  // any two refs can be interconverted
         }
@@ -109,21 +119,20 @@
      * Create a JVM-level adapter method handle to conform the given method
      * handle to the similar newType, using only pairwise argument conversions.
      * For each argument, convert incoming argument to the exact type needed.
-     * Only null conversions are allowed on the return value (until
-     * the JVM supports ricochet adapters).
-     * The argument conversions allowed are casting, unboxing,
+     * The argument conversions allowed are casting, boxing and unboxing,
      * integral widening or narrowing, and floating point widening or narrowing.
      * @param newType required call type
      * @param target original method handle
+     * @param level which strength of conversion is allowed
      * @return an adapter to the original handle with the desired new type,
      *          or the original target if the types are already identical
      *          or null if the adaptation cannot be made
      */
-    static MethodHandle makePairwiseConvert(MethodType newType, MethodHandle target) {
+    static MethodHandle makePairwiseConvert(MethodType newType, MethodHandle target, int level) {
         MethodType oldType = target.type();
         if (newType == oldType)  return target;
 
-        if (!canPairwiseConvert(newType, oldType))
+        if (!canPairwiseConvert(newType, oldType, level))
             return null;
         // (after this point, it is an assertion error to fail to convert)
 
@@ -138,9 +147,14 @@
                 break;
             }
         }
+
+        Class<?> needReturn = newType.returnType();
+        Class<?> haveReturn = oldType.returnType();
+        boolean retConv = !VerifyType.isNullConversion(haveReturn, needReturn);
+
         // Now build a chain of one or more adapters.
-        MethodHandle adapter = target;
-        MethodType midType = oldType.changeReturnType(newType.returnType());
+        MethodHandle adapter = target, adapter2;
+        MethodType midType = oldType;
         for (int i = 0; i <= lastConv; i++) {
             Class<?> src = newType.parameterType(i); // source type
             Class<?> dst = midType.parameterType(i); // destination type
@@ -149,22 +163,23 @@
                 continue;
             }
             // Work the current type backward toward the desired caller type:
-            if (i != lastConv) {
-                midType = midType.changeParameterType(i, src);
-            } else {
+            midType = midType.changeParameterType(i, src);
+            if (i == lastConv) {
                 // When doing the last (or only) real conversion,
                 // force all remaining null conversions to happen also.
-                assert(VerifyType.isNullConversion(newType, midType.changeParameterType(i, src)));
-                midType = newType;
+                MethodType lastMidType = newType;
+                if (retConv)  lastMidType = lastMidType.changeReturnType(haveReturn);
+                assert(VerifyType.isNullConversion(lastMidType, midType));
+                midType = lastMidType;
             }
 
             // Tricky case analysis follows.
             // It parallels canConvertArgument() above.
             if (src.isPrimitive()) {
                 if (dst.isPrimitive()) {
-                    adapter = makePrimCast(midType, adapter, i, dst);
+                    adapter2 = makePrimCast(midType, adapter, i, dst);
                 } else {
-                    adapter = makeBoxArgument(midType, adapter, i, dst);
+                    adapter2 = makeBoxArgument(midType, adapter, i, src);
                 }
             } else {
                 if (dst.isPrimitive()) {
@@ -174,29 +189,53 @@
                     // conversions supported by reflect.Method.invoke.
                     // Those conversions require a big nest of if/then/else logic,
                     // which we prefer to make a user responsibility.
-                    adapter = makeUnboxArgument(midType, adapter, i, dst);
+                    adapter2 = makeUnboxArgument(midType, adapter, i, dst, level);
                 } else {
                     // Simple reference conversion.
                     // Note:  Do not check for a class hierarchy relation
                     // between src and dst.  In all cases a 'null' argument
                     // will pass the cast conversion.
-                    adapter = makeCheckCast(midType, adapter, i, dst);
+                    adapter2 = makeCheckCast(midType, adapter, i, dst);
                 }
             }
-            assert(adapter != null);
-            assert(adapter.type() == midType);
+            assert(adapter2 != null) : Arrays.asList(src, dst, midType, adapter, i, target, newType);
+            assert(adapter2.type() == midType);
+            adapter = adapter2;
+        }
+        if (retConv) {
+            adapter2 = makeReturnConversion(adapter, haveReturn, needReturn);
+            assert(adapter2 != null);
+            adapter = adapter2;
         }
         if (adapter.type() != newType) {
             // Only trivial conversions remain.
-            adapter = makeRetypeOnly(newType, adapter);
-            assert(adapter != null);
+            adapter2 = makeRetypeOnly(newType, adapter);
+            assert(adapter2 != null);
+            adapter = adapter2;
             // Actually, that's because there were no non-trivial ones:
-            assert(lastConv == -1);
+            assert(lastConv == -1 || retConv);
         }
         assert(adapter.type() == newType);
         return adapter;
     }
 
+    private static MethodHandle makeReturnConversion(MethodHandle target, Class<?> haveReturn, Class<?> needReturn) {
+        MethodHandle adjustReturn;
+        if (haveReturn == void.class) {
+            // synthesize a zero value for the given void
+            Object zero = Wrapper.forBasicType(needReturn).zero();
+            adjustReturn = MethodHandles.constant(needReturn, zero);
+        } else {
+            MethodType needConversion = MethodType.methodType(needReturn, haveReturn);
+            adjustReturn = MethodHandles.identity(needReturn).asType(needConversion);
+        }
+        if (!canCollectArguments(adjustReturn.type(), target.type(), 0, false)) {
+            assert(MethodHandleNatives.workaroundWithoutRicochetFrames());  // this code is deprecated
+            throw new InternalError("NYI");
+        }
+        return makeCollectArguments(adjustReturn, target, 0, false);
+    }
+
     /**
      * Create a JVM-level adapter method handle to permute the arguments
      * of the given method.
@@ -224,7 +263,7 @@
         if (argumentMap.length != oldType.parameterCount())
             throw newIllegalArgumentException("bad permutation: "+Arrays.toString(argumentMap));
         if (nullPermutation) {
-            MethodHandle res = makePairwiseConvert(newType, target);
+            MethodHandle res = makePairwiseConvert(newType, target, 0);
             // well, that was easy
             if (res == null)
                 throw newIllegalArgumentException("cannot convert pairwise: "+newType);
@@ -310,11 +349,25 @@
         return (spChange & CONV_STACK_MOVE_MASK) << CONV_STACK_MOVE_SHIFT;
     }
 
+    static int extractStackMove(int convOp) {
+        int spChange = convOp >> CONV_STACK_MOVE_SHIFT;
+        return spChange / MethodHandleNatives.JVM_STACK_MOVE_UNIT;
+    }
+
+    static int extractStackMove(MethodHandle target) {
+        if (target instanceof AdapterMethodHandle) {
+            AdapterMethodHandle amh = (AdapterMethodHandle) target;
+            return extractStackMove(amh.getConversion());
+        } else {
+            return 0;
+        }
+    }
+
     /** Construct an adapter conversion descriptor for a single-argument conversion. */
     private static long makeConv(int convOp, int argnum, int src, int dest) {
-        assert(src  == (src  & 0xF));
-        assert(dest == (dest & 0xF));
-        assert(convOp >= OP_CHECK_CAST && convOp <= OP_PRIM_TO_REF);
+        assert(src  == (src  & CONV_TYPE_MASK));
+        assert(dest == (dest & CONV_TYPE_MASK));
+        assert(convOp >= OP_CHECK_CAST && convOp <= OP_PRIM_TO_REF || convOp == OP_COLLECT_ARGS);
         int stackMove = type2size(dest) - type2size(src);
         return ((long) argnum << 32 |
                 (long) convOp << CONV_OP_SHIFT |
@@ -323,11 +376,10 @@
                 insertStackMove(stackMove)
                 );
     }
-    private static long makeConv(int convOp, int argnum, int stackMove) {
-        assert(convOp >= OP_DUP_ARGS && convOp <= OP_SPREAD_ARGS);
+    private static long makeDupConv(int convOp, int argnum, int stackMove) {
+        // simple argument motion, requiring one slot to specify
+        assert(convOp == OP_DUP_ARGS || convOp == OP_DROP_ARGS);
         byte src = 0, dest = 0;
-        if (convOp >= OP_COLLECT_ARGS && convOp <= OP_SPREAD_ARGS)
-            src = dest = T_OBJECT;
         return ((long) argnum << 32 |
                 (long) convOp << CONV_OP_SHIFT |
                 (int)  src    << CONV_SRC_TYPE_SHIFT |
@@ -336,7 +388,8 @@
                 );
     }
     private static long makeSwapConv(int convOp, int srcArg, byte type, int destSlot) {
-        assert(convOp >= OP_SWAP_ARGS && convOp <= OP_ROT_ARGS);
+        // more complex argument motion, requiring two slots to specify
+        assert(convOp == OP_SWAP_ARGS || convOp == OP_ROT_ARGS);
         return ((long) srcArg << 32 |
                 (long) convOp << CONV_OP_SHIFT |
                 (int)  type   << CONV_SRC_TYPE_SHIFT |
@@ -344,6 +397,18 @@
                 (int)  destSlot << CONV_VMINFO_SHIFT
                 );
     }
+    private static long makeSpreadConv(int convOp, int argnum, int src, int dest, int stackMove) {
+        // spreading or collecting, at a particular slot location
+        assert(convOp == OP_SPREAD_ARGS || convOp == OP_COLLECT_ARGS || convOp == OP_FOLD_ARGS);
+        // src  = spread ? T_OBJECT (for array)  : common type of collected args (else void)
+        // dest = spread ? element type of array : result type of collector (can be void)
+        return ((long) argnum << 32 |
+                (long) convOp << CONV_OP_SHIFT |
+                (int)  src    << CONV_SRC_TYPE_SHIFT |
+                (int)  dest   << CONV_DEST_TYPE_SHIFT |
+                insertStackMove(stackMove)
+                );
+    }
     private static long makeConv(int convOp) {
         assert(convOp == OP_RETYPE_ONLY || convOp == OP_RETYPE_RAW);
         return ((long)-1 << 32) | (convOp << CONV_OP_SHIFT);   // stackMove, src, dst all zero
@@ -570,14 +635,10 @@
     static boolean canPrimCast(Class<?> src, Class<?> dst) {
         if (src == dst || !src.isPrimitive() || !dst.isPrimitive()) {
             return false;
-        } else if (Wrapper.forPrimitiveType(dst).isFloating()) {
-            // both must be floating types
-            return Wrapper.forPrimitiveType(src).isFloating();
         } else {
-            // both are integral, and all combinations work fine
-            assert(Wrapper.forPrimitiveType(src).isIntegral() &&
-                   Wrapper.forPrimitiveType(dst).isIntegral());
-            return true;
+            boolean sflt = Wrapper.forPrimitiveType(src).isFloating();
+            boolean dflt = Wrapper.forPrimitiveType(dst).isFloating();
+            return !(sflt | dflt);  // no float support at present
         }
     }
 
@@ -589,6 +650,29 @@
      */
     static MethodHandle makePrimCast(MethodType newType, MethodHandle target,
                 int arg, Class<?> convType) {
+        Class<?> src = newType.parameterType(arg);
+        if (canPrimCast(src, convType))
+            return makePrimCastOnly(newType, target, arg, convType);
+        Class<?> dst = convType;
+        boolean sflt = Wrapper.forPrimitiveType(src).isFloating();
+        boolean dflt = Wrapper.forPrimitiveType(dst).isFloating();
+        if (sflt | dflt) {
+            MethodHandle convMethod;
+            if (sflt)
+                convMethod = ((src == double.class)
+                        ? ValueConversions.convertFromDouble(dst)
+                        : ValueConversions.convertFromFloat(dst));
+            else
+                convMethod = ((dst == double.class)
+                        ? ValueConversions.convertToDouble(src)
+                        : ValueConversions.convertToFloat(src));
+            long conv = makeConv(OP_COLLECT_ARGS, arg, basicType(src), basicType(dst));
+            return new AdapterMethodHandle(target, newType, conv, convMethod);
+        }
+        throw new InternalError("makePrimCast");
+    }
+    static MethodHandle makePrimCastOnly(MethodType newType, MethodHandle target,
+                int arg, Class<?> convType) {
         MethodType oldType = target.type();
         if (!canPrimCast(newType, oldType, arg, convType))
             return null;
@@ -602,7 +686,7 @@
      *  The convType is the unboxed type; it can be either a primitive or wrapper.
      */
     static boolean canUnboxArgument(MethodType newType, MethodType targetType,
-                int arg, Class<?> convType) {
+                int arg, Class<?> convType, int level) {
         if (!convOpSupported(OP_REF_TO_PRIM))  return false;
         Class<?> src = newType.parameterType(arg);
         Class<?> dst = targetType.parameterType(arg);
@@ -616,21 +700,31 @@
         return (diff == arg+1);  // arg is sole non-trivial diff
     }
     /** Can an primitive unboxing adapter validly convert src to dst? */
-    static boolean canUnboxArgument(Class<?> src, Class<?> dst) {
-        return (!src.isPrimitive() && Wrapper.asPrimitiveType(dst).isPrimitive());
+    static boolean canUnboxArgument(Class<?> src, Class<?> dst, int level) {
+        assert(dst.isPrimitive());
+        // if we have JVM support for boxing, we can also do complex unboxing
+        if (convOpSupported(OP_PRIM_TO_REF))  return true;
+        Wrapper dw = Wrapper.forPrimitiveType(dst);
+        // Level 0 means cast and unbox.  This works on any reference.
+        if (level == 0)  return !src.isPrimitive();
+        assert(level >= 0 && level <= 2);
+        // Levels 1 and 2 allow widening and/or narrowing conversions.
+        // These are not supported directly by the JVM.
+        // But if the input reference is monomorphic, we can do it.
+        return dw.wrapperType() == src;
     }
 
     /** Factory method:  Unbox the given argument.
      *  Return null if this cannot be done.
      */
     static MethodHandle makeUnboxArgument(MethodType newType, MethodHandle target,
-                int arg, Class<?> convType) {
+                int arg, Class<?> convType, int level) {
         MethodType oldType = target.type();
         Class<?> src = newType.parameterType(arg);
         Class<?> dst = oldType.parameterType(arg);
         Class<?> boxType = Wrapper.asWrapperType(convType);
         Class<?> primType = Wrapper.asPrimitiveType(convType);
-        if (!canUnboxArgument(newType, oldType, arg, convType))
+        if (!canUnboxArgument(newType, oldType, arg, convType, level))
             return null;
         MethodType castDone = newType;
         if (!VerifyType.isNullConversion(src, boxType))
@@ -642,19 +736,46 @@
         return makeCheckCast(newType, adapter, arg, boxType);
     }
 
+    /** Can a boxing conversion validly convert src to dst? */
+    static boolean canBoxArgument(MethodType newType, MethodType targetType,
+                int arg, Class<?> convType) {
+        if (!convOpSupported(OP_PRIM_TO_REF))  return false;
+        Class<?> src = newType.parameterType(arg);
+        Class<?> dst = targetType.parameterType(arg);
+        Class<?> boxType = Wrapper.asWrapperType(convType);
+        convType = Wrapper.asPrimitiveType(convType);
+        if (!canCheckCast(boxType, dst)
+                || boxType == convType
+                || !VerifyType.isNullConversion(src, convType))
+            return false;
+        int diff = diffTypes(newType, targetType, false);
+        return (diff == arg+1);  // arg is sole non-trivial diff
+    }
+
     /** Can an primitive boxing adapter validly convert src to dst? */
     static boolean canBoxArgument(Class<?> src, Class<?> dst) {
         if (!convOpSupported(OP_PRIM_TO_REF))  return false;
-        throw new UnsupportedOperationException("NYI");
+        return (src.isPrimitive() && !dst.isPrimitive());
     }
 
-    /** Factory method:  Unbox the given argument.
+    /** Factory method:  Box the given argument.
      *  Return null if this cannot be done.
      */
     static MethodHandle makeBoxArgument(MethodType newType, MethodHandle target,
                 int arg, Class<?> convType) {
-        // this is difficult to do in the JVM because it must GC
-        return null;
+        MethodType oldType = target.type();
+        Class<?> src = newType.parameterType(arg);
+        Class<?> dst = oldType.parameterType(arg);
+        Class<?> boxType = Wrapper.asWrapperType(convType);
+        Class<?> primType = Wrapper.asPrimitiveType(convType);
+        if (!canBoxArgument(newType, oldType, arg, convType)) {
+            return null;
+        }
+        if (!VerifyType.isNullConversion(boxType, dst))
+            target = makeCheckCast(oldType.changeParameterType(arg, boxType), target, arg, dst);
+        MethodHandle boxerMethod = ValueConversions.box(Wrapper.forPrimitiveType(primType));
+        long conv = makeConv(OP_PRIM_TO_REF, arg, basicType(primType), T_OBJECT);
+        return new AdapterMethodHandle(target, newType, conv, boxerMethod);
     }
 
     /** Can an adapter simply drop arguments to convert the target to newType? */
@@ -699,7 +820,7 @@
         int slotCount   = keep1InSlot - dropSlot;
         assert(slotCount >= dropArgCount);
         assert(target.type().parameterSlotCount() + slotCount == newType.parameterSlotCount());
-        long conv = makeConv(OP_DROP_ARGS, dropArgPos + dropArgCount - 1, -slotCount);
+        long conv = makeDupConv(OP_DROP_ARGS, dropArgPos + dropArgCount - 1, -slotCount);
         return new AdapterMethodHandle(target, newType, conv);
     }
 
@@ -739,7 +860,7 @@
         int keep1InSlot = newType.parameterSlotDepth(dupArgPos);
         int slotCount   = keep1InSlot - dupSlot;
         assert(target.type().parameterSlotCount() - slotCount == newType.parameterSlotCount());
-        long conv = makeConv(OP_DUP_ARGS, dupArgPos + dupArgCount - 1, slotCount);
+        long conv = makeDupConv(OP_DUP_ARGS, dupArgPos + dupArgCount - 1, slotCount);
         return new AdapterMethodHandle(target, newType, conv);
     }
 
@@ -900,7 +1021,7 @@
         for (int i = 0; i < spreadArgCount; i++) {
             Class<?> src = VerifyType.spreadArgElementType(spreadArgType, i);
             Class<?> dst = targetType.parameterType(spreadArgPos + i);
-            if (src == null || !VerifyType.isNullConversion(src, dst))
+            if (src == null || !canConvertArgument(src, dst, 1))
                 return false;
         }
         return true;
@@ -910,24 +1031,100 @@
     /** Factory method:  Spread selected argument. */
     static MethodHandle makeSpreadArguments(MethodType newType, MethodHandle target,
                 Class<?> spreadArgType, int spreadArgPos, int spreadArgCount) {
+        // FIXME: Get rid of newType; derive new arguments from structure of spreadArgType
         MethodType targetType = target.type();
         if (!canSpreadArguments(newType, targetType, spreadArgType, spreadArgPos, spreadArgCount))
             return null;
+        // dest is not significant; remove?
+        int dest = T_VOID;
+        for (int i = 0; i < spreadArgCount; i++) {
+            Class<?> arg = VerifyType.spreadArgElementType(spreadArgType, i);
+            if (arg == null)  arg = Object.class;
+            int dest2 = basicType(arg);
+            if      (dest == T_VOID)  dest = dest2;
+            else if (dest != dest2)   dest = T_VOID;
+            if (dest == T_VOID)  break;
+            targetType = targetType.changeParameterType(spreadArgPos + i, arg);
+        }
+        target = target.asType(targetType);
+        int arrayArgSize = 1;  // always a reference
         // in  arglist: [0: ...keep1 | spos: spreadArg | spos+1:      keep2... ]
         // out arglist: [0: ...keep1 | spos: spread... | spos+scount: keep2... ]
         int keep2OutPos  = spreadArgPos + spreadArgCount;
-        int spreadSlot   = targetType.parameterSlotDepth(keep2OutPos);
-        int keep1OutSlot = targetType.parameterSlotDepth(spreadArgPos);
-        int slotCount    = keep1OutSlot - spreadSlot;
-        assert(spreadSlot == newType.parameterSlotDepth(spreadArgPos+1));
+        int keep1OutSlot = targetType.parameterSlotDepth(spreadArgPos);   // leading edge of |spread...|
+        int spreadSlot   = targetType.parameterSlotDepth(keep2OutPos);    // trailing edge of |spread...|
+        assert(spreadSlot == newType.parameterSlotDepth(spreadArgPos+arrayArgSize));
+        int slotCount    = keep1OutSlot - spreadSlot;                     // slots in |spread...|
         assert(slotCount >= spreadArgCount);
-        long conv = makeConv(OP_SPREAD_ARGS, spreadArgPos, slotCount-1);
+        int stackMove = - arrayArgSize + slotCount;  // pop array, push N slots
+        long conv = makeSpreadConv(OP_SPREAD_ARGS, spreadArgPos, T_OBJECT, dest, stackMove);
         MethodHandle res = new AdapterMethodHandle(target, newType, conv, spreadArgType);
         assert(res.type().parameterType(spreadArgPos) == spreadArgType);
         return res;
     }
 
-    // TO DO: makeCollectArguments, makeFlyby, makeRicochet
+    /** Can an adapter collect a series of arguments, replacing them by zero or one results? */
+    static boolean canCollectArguments(MethodType targetType,
+                MethodType collectorType, int collectArgPos, boolean retainOriginalArgs) {
+        if (!convOpSupported(retainOriginalArgs ? OP_FOLD_ARGS : OP_COLLECT_ARGS))  return false;
+        int collectArgCount = collectorType.parameterCount();
+        Class<?> rtype = collectorType.returnType();
+        assert(rtype == void.class || targetType.parameterType(collectArgPos) == rtype)
+                // [(Object)Object[], (Object[])Object[], 0, 1]
+                : Arrays.asList(targetType, collectorType, collectArgPos, collectArgCount)
+                ;
+        return true;
+    }
+
+    /** Factory method:  Collect or filter selected argument(s). */
+    static MethodHandle makeCollectArguments(MethodHandle target,
+                MethodHandle collector, int collectArgPos, boolean retainOriginalArgs) {
+        assert(canCollectArguments(target.type(), collector.type(), collectArgPos, retainOriginalArgs));
+        MethodType targetType = target.type();
+        MethodType collectorType = collector.type();
+        int collectArgCount = collectorType.parameterCount();
+        Class<?> collectValType = collectorType.returnType();
+        int collectValCount = (collectValType == void.class ? 0 : 1);
+        int collectValSlots = collectorType.returnSlotCount();
+        MethodType newType = targetType
+                .dropParameterTypes(collectArgPos, collectArgPos+collectValCount);
+        if (!retainOriginalArgs) {
+            newType = newType
+                .insertParameterTypes(collectArgPos, collectorType.parameterList());
+        } else {
+            // parameter types at the fold point must be the same
+            assert(diffParamTypes(newType, collectArgPos, targetType, collectValCount, collectArgCount, false) == 0)
+                : Arrays.asList(target, collector, collectArgPos, retainOriginalArgs);
+        }
+        // in  arglist: [0: ...keep1 | cpos: collect...  | cpos+cacount: keep2... ]
+        // out arglist: [0: ...keep1 | cpos: collectVal? | cpos+cvcount: keep2... ]
+        // out(retain): [0: ...keep1 | cpos: cV? coll... | cpos+cvc+cac: keep2... ]
+        int keep2InPos   = collectArgPos + collectArgCount;
+        int keep1InSlot  = newType.parameterSlotDepth(collectArgPos);  // leading edge of |collect...|
+        int collectSlot  = newType.parameterSlotDepth(keep2InPos);     // trailing edge of |collect...|
+        int slotCount    = keep1InSlot - collectSlot;                  // slots in |collect...|
+        assert(slotCount >= collectArgCount);
+        assert(collectSlot == targetType.parameterSlotDepth(
+                collectArgPos + collectValCount + (retainOriginalArgs ? collectArgCount : 0) ));
+        int dest = basicType(collectValType);
+        int src = T_VOID;
+        // src is not significant; remove?
+        for (int i = 0; i < collectArgCount; i++) {
+            int src2 = basicType(collectorType.parameterType(i));
+            if      (src == T_VOID)  src = src2;
+            else if (src != src2)    src = T_VOID;
+            if (src == T_VOID)  break;
+        }
+        int stackMove = collectValSlots;  // push 0..2 results
+        if (!retainOriginalArgs)  stackMove -= slotCount; // pop N arguments
+        int lastCollectArg = keep2InPos-1;
+        long conv = makeSpreadConv(retainOriginalArgs ? OP_FOLD_ARGS : OP_COLLECT_ARGS,
+                                   lastCollectArg, src, dest, stackMove);
+        MethodHandle res = new AdapterMethodHandle(target, newType, conv, collector);
+        assert(res.type().parameterList().subList(collectArgPos, collectArgPos+collectArgCount)
+                .equals(collector.type().parameterList()));
+        return res;
+    }
 
     @Override
     public String toString() {