# HG changeset patch # User trims # Date 1305667799 25200 # Node ID 9fc3d991dc634ac5d48e1f954e0296734822c30f # Parent 04f88d98efe35a1e0362788519ab6ac3a0d9a79f# Parent 5ebbe5ab084f44e76ce0edd7248852da0dcd8114 Merge diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/AdapterMethodHandle.java --- a/jdk/src/share/classes/java/lang/invoke/AdapterMethodHandle.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/AdapterMethodHandle.java Tue May 17 14:29:59 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() { diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/CallSite.java --- a/jdk/src/share/classes/java/lang/invoke/CallSite.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/CallSite.java Tue May 17 14:29:59 2011 -0700 @@ -273,9 +273,9 @@ Object binding; info = maybeReBox(info); if (info == null) { - binding = bootstrapMethod.invokeGeneric(caller, name, type); + binding = bootstrapMethod.invoke(caller, name, type); } else if (!info.getClass().isArray()) { - binding = bootstrapMethod.invokeGeneric(caller, name, type, info); + binding = bootstrapMethod.invoke(caller, name, type, info); } else { Object[] argv = (Object[]) info; maybeReBoxElements(argv); @@ -283,10 +283,10 @@ throw new BootstrapMethodError("too many bootstrap method arguments"); MethodType bsmType = bootstrapMethod.type(); if (bsmType.parameterCount() == 4 && bsmType.parameterType(3) == Object[].class) - binding = bootstrapMethod.invokeGeneric(caller, name, type, argv); + binding = bootstrapMethod.invoke(caller, name, type, argv); else binding = MethodHandles.spreadInvoker(bsmType, 3) - .invokeGeneric(bootstrapMethod, caller, name, type, argv); + .invoke(bootstrapMethod, caller, name, type, argv); } //System.out.println("BSM for "+name+type+" => "+binding); if (binding instanceof CallSite) { diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/FilterGeneric.java --- a/jdk/src/share/classes/java/lang/invoke/FilterGeneric.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/FilterGeneric.java Tue May 17 14:29:59 2011 -0700 @@ -61,6 +61,10 @@ return ad; } + static { + assert(MethodHandleNatives.workaroundWithoutRicochetFrames()); // this class is deprecated + } + Adapter makeInstance(Kind kind, int pos, MethodHandle filter, MethodHandle target) { Adapter ad = getAdapter(kind, pos); return ad.makeInstance(ad.prototypeEntryPoint(), filter, target); diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/FilterOneArgument.java --- a/jdk/src/share/classes/java/lang/invoke/FilterOneArgument.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/FilterOneArgument.java Tue May 17 14:29:59 2011 -0700 @@ -67,6 +67,10 @@ this.target = target; } + static { + assert(MethodHandleNatives.workaroundWithoutRicochetFrames()); // this class is deprecated + } + public static MethodHandle make(MethodHandle filter, MethodHandle target) { if (filter == null) return target; if (target == null) return filter; diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/FromGeneric.java --- a/jdk/src/share/classes/java/lang/invoke/FromGeneric.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/FromGeneric.java Tue May 17 14:29:59 2011 -0700 @@ -98,6 +98,10 @@ this.unboxingInvoker = computeUnboxingInvoker(targetType, internalType0); } + static { + assert(MethodHandleNatives.workaroundWithoutRicochetFrames()); // this class is deprecated + } + /** * The typed target will be called according to targetType. * The adapter code will in fact see the raw result from internalType, @@ -112,10 +116,10 @@ assert(iret == Object.class); return ValueConversions.identity(); } else if (wrap.primitiveType() == iret) { - return ValueConversions.box(wrap, false); + return ValueConversions.box(wrap); } else { assert(tret == double.class ? iret == long.class : iret == int.class); - return ValueConversions.boxRaw(wrap, false); + return ValueConversions.boxRaw(wrap); } } @@ -135,7 +139,7 @@ MethodType fixArgsType = internalType.changeReturnType(targetType.returnType()); MethodHandle fixArgs = MethodHandleImpl.convertArguments( invoker, Invokers.invokerType(fixArgsType), - invoker.type(), null); + invoker.type(), 0); if (fixArgs == null) throw new InternalError("bad fixArgs"); // reinterpret the calling sequence as raw: @@ -160,7 +164,6 @@ /** Build an adapter of the given generic type, which invokes typedTarget * on the incoming arguments, after unboxing as necessary. * The return value is boxed if necessary. - * @param genericType the required type of the result * @param typedTarget the target * @return an adapter method handle */ @@ -231,7 +234,7 @@ } static Adapter buildAdapterFromBytecodes(MethodType internalType) { - throw new UnsupportedOperationException("NYI"); + throw new UnsupportedOperationException("NYI "+internalType); } /** diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/InvokeGeneric.java --- a/jdk/src/share/classes/java/lang/invoke/InvokeGeneric.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/InvokeGeneric.java Tue May 17 14:29:59 2011 -0700 @@ -29,12 +29,12 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; /** - * Adapters which manage MethodHandle.invokeGeneric calls. + * Adapters which manage inexact MethodHandle.invoke calls. * The JVM calls one of these when the exact type match fails. * @author jrose */ class InvokeGeneric { - // erased type for the call, which originates from an invokeGeneric site + // erased type for the call, which originates from an inexact invoke site private final MethodType erasedCallerType; // an invoker of type (MT, MH; A...) -> R private final MethodHandle initialInvoker; @@ -56,7 +56,7 @@ } /** Return the adapter information for this type's erasure. */ - /*non-public*/ static MethodHandle genericInvokerOf(MethodType erasedCallerType) throws ReflectiveOperationException { + /*non-public*/ static MethodHandle generalInvokerOf(MethodType erasedCallerType) throws ReflectiveOperationException { InvokeGeneric gen = new InvokeGeneric(erasedCallerType); return gen.initialInvoker; } diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/Invokers.java --- a/jdk/src/share/classes/java/lang/invoke/Invokers.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/Invokers.java Tue May 17 14:29:59 2011 -0700 @@ -43,10 +43,10 @@ private /*lazy*/ MethodHandle erasedInvoker; /*lazy*/ MethodHandle erasedInvokerWithDrops; // for InvokeGeneric - // generic (untyped) invoker for the outgoing call - private /*lazy*/ MethodHandle genericInvoker; + // general invoker for the outgoing call + private /*lazy*/ MethodHandle generalInvoker; - // generic (untyped) invoker for the outgoing call; accepts a single Object[] + // general invoker for the outgoing call; accepts a single Object[] private final /*lazy*/ MethodHandle[] spreadInvokers; // invoker for an unbound callsite @@ -77,13 +77,13 @@ return invoker; } - /*non-public*/ MethodHandle genericInvoker() { + /*non-public*/ MethodHandle generalInvoker() { MethodHandle invoker1 = exactInvoker(); - MethodHandle invoker = genericInvoker; + MethodHandle invoker = generalInvoker; if (invoker != null) return invoker; - MethodType genericType = targetType.generic(); - invoker = MethodHandles.convertArguments(invoker1, invokerType(genericType)); - genericInvoker = invoker; + MethodType generalType = targetType.generic(); + invoker = invoker1.asType(invokerType(generalType)); + generalInvoker = invoker; return invoker; } @@ -93,9 +93,9 @@ if (invoker != null) return invoker; MethodType erasedType = targetType.erase(); if (erasedType == targetType.generic()) - invoker = genericInvoker(); + invoker = generalInvoker(); else - invoker = MethodHandles.convertArguments(invoker1, invokerType(erasedType)); + invoker = invoker1.asType(invokerType(erasedType)); erasedInvoker = invoker; return invoker; } @@ -103,7 +103,7 @@ /*non-public*/ MethodHandle spreadInvoker(int objectArgCount) { MethodHandle vaInvoker = spreadInvokers[objectArgCount]; if (vaInvoker != null) return vaInvoker; - MethodHandle gInvoker = genericInvoker(); + MethodHandle gInvoker = generalInvoker(); vaInvoker = gInvoker.asSpreader(Object[].class, targetType.parameterCount() - objectArgCount); spreadInvokers[objectArgCount] = vaInvoker; return vaInvoker; diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/MemberName.java --- a/jdk/src/share/classes/java/lang/invoke/MemberName.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/MemberName.java Tue May 17 14:29:59 2011 -0700 @@ -525,7 +525,7 @@ /** A factory type for resolving member names with the help of the VM. * TBD: Define access-safe public constructors for this factory. */ - public static class Factory { + /*non-public*/ static class Factory { private Factory() { } // singleton pattern static Factory INSTANCE = new Factory(); diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/MethodHandle.java --- a/jdk/src/share/classes/java/lang/invoke/MethodHandle.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandle.java Tue May 17 14:29:59 2011 -0700 @@ -26,6 +26,7 @@ package java.lang.invoke; +import sun.invoke.util.ValueConversions; import static java.lang.invoke.MethodHandleStatics.*; /** @@ -53,12 +54,12 @@ * and the kinds of transformations that apply to it. *

* A method handle contains a pair of special invoker methods - * called {@link #invokeExact invokeExact} and {@link #invokeGeneric invokeGeneric}. + * called {@link #invokeExact invokeExact} and {@link #invoke invoke}. * Both invoker methods provide direct access to the method handle's * underlying method, constructor, field, or other operation, * as modified by transformations of arguments and return values. * Both invokers accept calls which exactly match the method handle's own type. - * The {@code invokeGeneric} invoker also accepts a range of other call types. + * The plain, inexact invoker also accepts a range of other call types. *

* Method handles are immutable and have no visible state. * Of course, they can be bound to underlying methods or data which exhibit state. @@ -76,7 +77,7 @@ * may change from time to time or across implementations from different vendors. * *

Method handle compilation

- * A Java method call expression naming {@code invokeExact} or {@code invokeGeneric} + * A Java method call expression naming {@code invokeExact} or {@code invoke} * can invoke a method handle from Java source code. * From the viewpoint of source code, these methods can take any arguments * and their result can be cast to any return type. @@ -86,7 +87,7 @@ * which connects this freedom of invocation directly to the JVM execution stack. *

* As is usual with virtual methods, source-level calls to {@code invokeExact} - * and {@code invokeGeneric} compile to an {@code invokevirtual} instruction. + * and {@code invoke} compile to an {@code invokevirtual} instruction. * More unusually, the compiler must record the actual argument types, * and may not perform method invocation conversions on the arguments. * Instead, it must push them on the stack according to their own unconverted types. @@ -109,7 +110,7 @@ * The first time a {@code invokevirtual} instruction is executed * it is linked, by symbolically resolving the names in the instruction * and verifying that the method call is statically legal. - * This is true of calls to {@code invokeExact} and {@code invokeGeneric}. + * This is true of calls to {@code invokeExact} and {@code invoke}. * In this case, the type descriptor emitted by the compiler is checked for * correct syntax and names it contains are resolved. * Thus, an {@code invokevirtual} instruction which invokes @@ -127,18 +128,18 @@ * In the case of {@code invokeExact}, the type descriptor of the invocation * (after resolving symbolic type names) must exactly match the method type * of the receiving method handle. - * In the case of {@code invokeGeneric}, the resolved type descriptor + * In the case of plain, inexact {@code invoke}, the resolved type descriptor * must be a valid argument to the receiver's {@link #asType asType} method. - * Thus, {@code invokeGeneric} is more permissive than {@code invokeExact}. + * Thus, plain {@code invoke} is more permissive than {@code invokeExact}. *

* After type matching, a call to {@code invokeExact} directly * and immediately invoke the method handle's underlying method * (or other behavior, as the case may be). *

- * A call to {@code invokeGeneric} works the same as a call to + * A call to plain {@code invoke} works the same as a call to * {@code invokeExact}, if the type descriptor specified by the caller * exactly matches the method handle's own type. - * If there is a type mismatch, {@code invokeGeneric} attempts + * If there is a type mismatch, {@code invoke} attempts * to adjust the type of the receiving method handle, * as if by a call to {@link #asType asType}, * to obtain an exactly invokable method handle {@code M2}. @@ -152,7 +153,7 @@ * In typical programs, method handle type matching will usually succeed. * But if a match fails, the JVM will throw a {@link WrongMethodTypeException}, * either directly (in the case of {@code invokeExact}) or indirectly as if - * by a failed call to {@code asType} (in the case of {@code invokeGeneric}). + * by a failed call to {@code asType} (in the case of {@code invoke}). *

* Thus, a method type mismatch which might show up as a linkage error * in a statically typed program can show up as @@ -249,8 +250,8 @@ mt = MethodType.methodType(java.util.List.class, Object[].class); mh = lookup.findStatic(java.util.Arrays.class, "asList", mt); assert(mh.isVarargsCollector()); -x = mh.invokeGeneric("one", "two"); -// invokeGeneric(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; +x = mh.invoke("one", "two"); +// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; assert(x.equals(java.util.Arrays.asList("one","two"))); // mt is (Object,Object,Object)Object mt = MethodType.genericMethodType(3); @@ -269,12 +270,12 @@ mh.invokeExact(System.out, "Hello, world."); // invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V * - * Each of the above calls to {@code invokeExact} or {@code invokeGeneric} + * Each of the above calls to {@code invokeExact} or plain {@code invoke} * generates a single invokevirtual instruction with * the type descriptor indicated in the following comment. * *

Exceptions

- * The methods {@code invokeExact} and {@code invokeGeneric} are declared + * The methods {@code invokeExact} and {@code invoke} are declared * to throw {@link java.lang.Throwable Throwable}, * which is to say that there is no static restriction on what a method handle * can throw. Since the JVM does not distinguish between checked @@ -288,7 +289,7 @@ * *

Signature polymorphism

* The unusual compilation and linkage behavior of - * {@code invokeExact} and {@code invokeGeneric} + * {@code invokeExact} and plain {@code invoke} * is referenced by the term signature polymorphism. * A signature polymorphic method is one which can operate with * any of a wide range of call signatures and return types. @@ -322,7 +323,7 @@ * The following methods (and no others) are signature polymorphic: * *

* A signature polymorphic method will be declared with the following properties: @@ -374,24 +375,34 @@ *

* As a special case, * when the Core Reflection API is used to view the signature polymorphic - * methods {@code invokeExact} or {@code invokeGeneric} in this class, - * they appear as single, non-polymorphic native methods. - * Calls to these native methods do not result in method handle invocations. + * methods {@code invokeExact} or plain {@code invoke} in this class, + * they appear as ordinary non-polymorphic methods. + * Their reflective appearance, as viewed by + * {@link java.lang.Class#getDeclaredMethod Class.getDeclaredMethod}, + * is unaffected by their special status in this API. + * For example, {@link java.lang.reflect.Method#getModifiers Method.getModifiers} + * will report exactly those modifier bits required for any similarly + * declared method, including in this case {@code native} and {@code varargs} bits. + *

+ * As with any reflected method, these methods (when reflected) may be + * invoked via {@link java.lang.reflect.Method#invoke Method.invoke}. + * However, such reflective calls do not result in method handle invocations. + * Such a call, if passed the required argument + * (a single one, of type {@code Object[]}), will ignore the argument and + * will throw an {@code UnsupportedOperationException}. + *

* Since {@code invokevirtual} instructions can natively * invoke method handles under any type descriptor, this reflective view conflicts - * with the normal presentation via bytecodes. - * Thus, these two native methods, as viewed by - * {@link java.lang.Class#getDeclaredMethod Class.getDeclaredMethod}, - * are placeholders only. - * If invoked via {@link java.lang.reflect.Method#invoke Method.invoke}, - * they will throw {@code UnsupportedOperationException}. + * with the normal presentation of these methods via bytecodes. + * Thus, these two native methods, when reflectively viewed by + * {@code Class.getDeclaredMethod}, may be regarded as placeholders only. *

* In order to obtain an invoker method for a particular type descriptor, * use {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker}, - * or {@link java.lang.invoke.MethodHandles#genericInvoker MethodHandles.genericInvoker}. + * or {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}. * The {@link java.lang.invoke.MethodHandles.Lookup#findVirtual Lookup.findVirtual} * API is also able to return a method handle - * to call {@code invokeExact} or {@code invokeGeneric}, + * to call {@code invokeExact} or plain {@code invoke}, * for any specified type descriptor . * *

Interoperation between method handles and Java generics

@@ -523,7 +534,7 @@ * adaptations directly on the caller's arguments, * and call the target method handle according to its own exact type. *

- * The type descriptor at the call site of {@code invokeGeneric} must + * The type descriptor at the call site of {@code invoke} must * be a valid argument to the receivers {@code asType} method. * In particular, the caller must specify the same argument arity * as the callee's type, @@ -539,11 +550,18 @@ * @throws ClassCastException if the target's type can be adjusted to the caller, but a reference cast fails * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call */ + public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable; + + /** + * Temporary alias for {@link #invoke}, for backward compatibility with some versions of JSR 292. + * On some JVMs, support can be excluded by the flags {@code -XX:+UnlockExperimentalVMOptions -XX:-AllowInvokeGeneric}. + * @deprecated Will be removed for JSR 292 Proposed Final Draft. + */ public final native @PolymorphicSignature Object invokeGeneric(Object... args) throws Throwable; /** * Performs a varargs invocation, passing the arguments in the given array - * to the method handle, as if via {@link #invokeGeneric invokeGeneric} from a call site + * to the method handle, as if via an inexact {@link #invoke invoke} from a call site * which mentions only the type {@code Object}, and whose arity is the length * of the argument array. *

@@ -553,7 +571,7 @@ *

- * @return true if this method handle accepts more than one arity of {@code invokeGeneric} calls + * @return true if this method handle accepts more than one arity of plain, inexact {@code invoke} calls * @see #asVarargsCollector */ public boolean isVarargsCollector() { diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java --- a/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java Tue May 17 14:29:59 2011 -0700 @@ -121,11 +121,11 @@ if (nargs < INVOKES.length) { MethodHandle invoke = INVOKES[nargs]; MethodType conType = CON_TYPES[nargs]; - MethodHandle gcon = convertArguments(rawConstructor, conType, rawConType, null); + MethodHandle gcon = convertArguments(rawConstructor, conType, rawConType, 0); if (gcon == null) return null; MethodHandle galloc = new AllocateObject(invoke, allocateClass, gcon); assert(galloc.type() == newType.generic()); - return convertArguments(galloc, newType, galloc.type(), null); + return convertArguments(galloc, newType, galloc.type(), 0); } else { MethodHandle invoke = VARARGS_INVOKE; MethodType conType = CON_TYPES[nargs]; @@ -256,8 +256,8 @@ FieldAccessor.ahandle(arrayClass, true) }; if (mhs[0].type().parameterType(0) == Class.class) { - mhs[0] = MethodHandles.insertArguments(mhs[0], 0, elemClass); - mhs[1] = MethodHandles.insertArguments(mhs[1], 0, elemClass); + mhs[0] = mhs[0].bindTo(elemClass); + mhs[1] = mhs[1].bindTo(elemClass); } synchronized (FieldAccessor.ARRAY_CACHE) {} // memory barrier FieldAccessor.ARRAY_CACHE.put(elemClass, mhs); @@ -372,7 +372,7 @@ if (evclass != vclass || (!isStatic && ecclass != cclass)) { MethodType strongType = FieldAccessor.ftype(cclass, vclass, isSetter, isStatic); strongType = strongType.insertParameterTypes(0, FieldAccessor.class); - mh = MethodHandles.convertArguments(mh, strongType); + mh = convertArguments(mh, strongType, 0); } return mh; } @@ -439,8 +439,8 @@ } if (caclass != null) { MethodType strongType = FieldAccessor.atype(caclass, isSetter); - mh = MethodHandles.insertArguments(mh, 0, caclass); - mh = MethodHandles.convertArguments(mh, strongType); + mh = mh.bindTo(caclass); + mh = convertArguments(mh, strongType, 0); } return mh; } @@ -465,7 +465,7 @@ dmh.type().parameterType(0).isAssignableFrom(receiver.getClass())) { MethodHandle bmh = new BoundMethodHandle(dmh, receiver, 0); MethodType newType = target.type().dropParameterTypes(0, 1); - return convertArguments(bmh, newType, bmh.type(), null); + return convertArguments(bmh, newType, bmh.type(), 0); } } } @@ -486,301 +486,378 @@ return new BoundMethodHandle(target, receiver, argnum); } - static MethodHandle convertArguments(MethodHandle target, + static MethodHandle permuteArguments(MethodHandle target, MethodType newType, MethodType oldType, int[] permutationOrNull) { assert(oldType.parameterCount() == target.type().parameterCount()); - if (permutationOrNull != null) { - int outargs = oldType.parameterCount(), inargs = newType.parameterCount(); - if (permutationOrNull.length != outargs) - throw newIllegalArgumentException("wrong number of arguments in permutation"); - // Make the individual outgoing argument types match up first. - Class[] callTypeArgs = new Class[outargs]; - for (int i = 0; i < outargs; i++) - callTypeArgs[i] = newType.parameterType(permutationOrNull[i]); - MethodType callType = MethodType.methodType(oldType.returnType(), callTypeArgs); - target = convertArguments(target, callType, oldType, null); - assert(target != null); - oldType = target.type(); - List goal = new ArrayList(); // i*TOKEN - List state = new ArrayList(); // i*TOKEN - List drops = new ArrayList(); // not tokens - List dups = new ArrayList(); // not tokens - final int TOKEN = 10; // to mark items which are symbolic only - // state represents the argument values coming into target - for (int i = 0; i < outargs; i++) { - state.add(permutationOrNull[i] * TOKEN); + int outargs = oldType.parameterCount(), inargs = newType.parameterCount(); + if (permutationOrNull.length != outargs) + throw newIllegalArgumentException("wrong number of arguments in permutation"); + // Make the individual outgoing argument types match up first. + Class[] callTypeArgs = new Class[outargs]; + for (int i = 0; i < outargs; i++) + callTypeArgs[i] = newType.parameterType(permutationOrNull[i]); + MethodType callType = MethodType.methodType(oldType.returnType(), callTypeArgs); + target = convertArguments(target, callType, oldType, 0); + assert(target != null); + oldType = target.type(); + List goal = new ArrayList(); // i*TOKEN + List state = new ArrayList(); // i*TOKEN + List drops = new ArrayList(); // not tokens + List dups = new ArrayList(); // not tokens + final int TOKEN = 10; // to mark items which are symbolic only + // state represents the argument values coming into target + for (int i = 0; i < outargs; i++) { + state.add(permutationOrNull[i] * TOKEN); + } + // goal represents the desired state + for (int i = 0; i < inargs; i++) { + if (state.contains(i * TOKEN)) { + goal.add(i * TOKEN); + } else { + // adapter must initially drop all unused arguments + drops.add(i); } - // goal represents the desired state - for (int i = 0; i < inargs; i++) { - if (state.contains(i * TOKEN)) { - goal.add(i * TOKEN); - } else { - // adapter must initially drop all unused arguments - drops.add(i); + } + // detect duplications + while (state.size() > goal.size()) { + for (int i2 = 0; i2 < state.size(); i2++) { + int arg1 = state.get(i2); + int i1 = state.indexOf(arg1); + if (i1 != i2) { + // found duplicate occurrence at i2 + int arg2 = (inargs++) * TOKEN; + state.set(i2, arg2); + dups.add(goal.indexOf(arg1)); + goal.add(arg2); } } - // detect duplications - while (state.size() > goal.size()) { - for (int i2 = 0; i2 < state.size(); i2++) { - int arg1 = state.get(i2); - int i1 = state.indexOf(arg1); - if (i1 != i2) { - // found duplicate occurrence at i2 - int arg2 = (inargs++) * TOKEN; - state.set(i2, arg2); - dups.add(goal.indexOf(arg1)); - goal.add(arg2); + } + assert(state.size() == goal.size()); + int size = goal.size(); + while (!state.equals(goal)) { + // Look for a maximal sequence of adjacent misplaced arguments, + // and try to rotate them into place. + int bestRotArg = -10 * TOKEN, bestRotLen = 0; + int thisRotArg = -10 * TOKEN, thisRotLen = 0; + for (int i = 0; i < size; i++) { + int arg = state.get(i); + // Does this argument match the current run? + if (arg == thisRotArg + TOKEN) { + thisRotArg = arg; + thisRotLen += 1; + if (bestRotLen < thisRotLen) { + bestRotLen = thisRotLen; + bestRotArg = thisRotArg; + } + } else { + // The old sequence (if any) stops here. + thisRotLen = 0; + thisRotArg = -10 * TOKEN; + // But maybe a new one starts here also. + int wantArg = goal.get(i); + final int MAX_ARG_ROTATION = AdapterMethodHandle.MAX_ARG_ROTATION; + if (arg != wantArg && + arg >= wantArg - TOKEN * MAX_ARG_ROTATION && + arg <= wantArg + TOKEN * MAX_ARG_ROTATION) { + thisRotArg = arg; + thisRotLen = 1; } } } - assert(state.size() == goal.size()); - int size = goal.size(); - while (!state.equals(goal)) { - // Look for a maximal sequence of adjacent misplaced arguments, - // and try to rotate them into place. - int bestRotArg = -10 * TOKEN, bestRotLen = 0; - int thisRotArg = -10 * TOKEN, thisRotLen = 0; - for (int i = 0; i < size; i++) { - int arg = state.get(i); - // Does this argument match the current run? - if (arg == thisRotArg + TOKEN) { - thisRotArg = arg; - thisRotLen += 1; - if (bestRotLen < thisRotLen) { - bestRotLen = thisRotLen; - bestRotArg = thisRotArg; - } - } else { - // The old sequence (if any) stops here. - thisRotLen = 0; - thisRotArg = -10 * TOKEN; - // But maybe a new one starts here also. - int wantArg = goal.get(i); - final int MAX_ARG_ROTATION = AdapterMethodHandle.MAX_ARG_ROTATION; - if (arg != wantArg && - arg >= wantArg - TOKEN * MAX_ARG_ROTATION && - arg <= wantArg + TOKEN * MAX_ARG_ROTATION) { - thisRotArg = arg; - thisRotLen = 1; - } + if (bestRotLen >= 2) { + // Do a rotation if it can improve argument positioning + // by at least 2 arguments. This is not always optimal, + // but it seems to catch common cases. + int dstEnd = state.indexOf(bestRotArg); + int srcEnd = goal.indexOf(bestRotArg); + int rotBy = dstEnd - srcEnd; + int dstBeg = dstEnd - (bestRotLen - 1); + int srcBeg = srcEnd - (bestRotLen - 1); + assert((dstEnd | dstBeg | srcEnd | srcBeg) >= 0); // no negs + // Make a span which covers both source and destination. + int rotBeg = Math.min(dstBeg, srcBeg); + int rotEnd = Math.max(dstEnd, srcEnd); + int score = 0; + for (int i = rotBeg; i <= rotEnd; i++) { + if ((int)state.get(i) != (int)goal.get(i)) + score += 1; + } + List rotSpan = state.subList(rotBeg, rotEnd+1); + Collections.rotate(rotSpan, -rotBy); // reverse direction + for (int i = rotBeg; i <= rotEnd; i++) { + if ((int)state.get(i) != (int)goal.get(i)) + score -= 1; + } + if (score >= 2) { + // Improved at least two argument positions. Do it. + List> ptypes = Arrays.asList(oldType.parameterArray()); + Collections.rotate(ptypes.subList(rotBeg, rotEnd+1), -rotBy); + MethodType rotType = MethodType.methodType(oldType.returnType(), ptypes); + MethodHandle nextTarget + = AdapterMethodHandle.makeRotateArguments(rotType, target, + rotBeg, rotSpan.size(), rotBy); + if (nextTarget != null) { + //System.out.println("Rot: "+rotSpan+" by "+rotBy); + target = nextTarget; + oldType = rotType; + continue; } } - if (bestRotLen >= 2) { - // Do a rotation if it can improve argument positioning - // by at least 2 arguments. This is not always optimal, - // but it seems to catch common cases. - int dstEnd = state.indexOf(bestRotArg); - int srcEnd = goal.indexOf(bestRotArg); - int rotBy = dstEnd - srcEnd; - int dstBeg = dstEnd - (bestRotLen - 1); - int srcBeg = srcEnd - (bestRotLen - 1); - assert((dstEnd | dstBeg | srcEnd | srcBeg) >= 0); // no negs - // Make a span which covers both source and destination. - int rotBeg = Math.min(dstBeg, srcBeg); - int rotEnd = Math.max(dstEnd, srcEnd); - int score = 0; - for (int i = rotBeg; i <= rotEnd; i++) { - if ((int)state.get(i) != (int)goal.get(i)) - score += 1; - } - List rotSpan = state.subList(rotBeg, rotEnd+1); - Collections.rotate(rotSpan, -rotBy); // reverse direction - for (int i = rotBeg; i <= rotEnd; i++) { - if ((int)state.get(i) != (int)goal.get(i)) - score -= 1; - } - if (score >= 2) { - // Improved at least two argument positions. Do it. - List> ptypes = Arrays.asList(oldType.parameterArray()); - Collections.rotate(ptypes.subList(rotBeg, rotEnd+1), -rotBy); - MethodType rotType = MethodType.methodType(oldType.returnType(), ptypes); - MethodHandle nextTarget - = AdapterMethodHandle.makeRotateArguments(rotType, target, - rotBeg, rotSpan.size(), rotBy); - if (nextTarget != null) { - //System.out.println("Rot: "+rotSpan+" by "+rotBy); - target = nextTarget; - oldType = rotType; - continue; - } - } - // Else de-rotate, and drop through to the swap-fest. - Collections.rotate(rotSpan, rotBy); - } + // Else de-rotate, and drop through to the swap-fest. + Collections.rotate(rotSpan, rotBy); + } - // Now swap like the wind! - List> ptypes = Arrays.asList(oldType.parameterArray()); - for (int i = 0; i < size; i++) { - // What argument do I want here? - int arg = goal.get(i); - if (arg != state.get(i)) { - // Where is it now? - int j = state.indexOf(arg); - Collections.swap(ptypes, i, j); - MethodType swapType = MethodType.methodType(oldType.returnType(), ptypes); - target = AdapterMethodHandle.makeSwapArguments(swapType, target, i, j); - if (target == null) throw newIllegalArgumentException("cannot swap"); - assert(target.type() == swapType); - oldType = swapType; - Collections.swap(state, i, j); - } + // Now swap like the wind! + List> ptypes = Arrays.asList(oldType.parameterArray()); + for (int i = 0; i < size; i++) { + // What argument do I want here? + int arg = goal.get(i); + if (arg != state.get(i)) { + // Where is it now? + int j = state.indexOf(arg); + Collections.swap(ptypes, i, j); + MethodType swapType = MethodType.methodType(oldType.returnType(), ptypes); + target = AdapterMethodHandle.makeSwapArguments(swapType, target, i, j); + if (target == null) throw newIllegalArgumentException("cannot swap"); + assert(target.type() == swapType); + oldType = swapType; + Collections.swap(state, i, j); } - // One pass of swapping must finish the job. - assert(state.equals(goal)); + } + // One pass of swapping must finish the job. + assert(state.equals(goal)); + } + while (!dups.isEmpty()) { + // Grab a contiguous trailing sequence of dups. + int grab = dups.size() - 1; + int dupArgPos = dups.get(grab), dupArgCount = 1; + while (grab - 1 >= 0) { + int dup0 = dups.get(grab - 1); + if (dup0 != dupArgPos - 1) break; + dupArgPos -= 1; + dupArgCount += 1; + grab -= 1; + } + //if (dupArgCount > 1) System.out.println("Dup: "+dups.subList(grab, dups.size())); + dups.subList(grab, dups.size()).clear(); + // In the new target type drop that many args from the tail: + List> ptypes = oldType.parameterList(); + ptypes = ptypes.subList(0, ptypes.size() - dupArgCount); + MethodType dupType = MethodType.methodType(oldType.returnType(), ptypes); + target = AdapterMethodHandle.makeDupArguments(dupType, target, dupArgPos, dupArgCount); + if (target == null) + throw newIllegalArgumentException("cannot dup"); + oldType = target.type(); + } + while (!drops.isEmpty()) { + // Grab a contiguous initial sequence of drops. + int dropArgPos = drops.get(0), dropArgCount = 1; + while (dropArgCount < drops.size()) { + int drop1 = drops.get(dropArgCount); + if (drop1 != dropArgPos + dropArgCount) break; + dropArgCount += 1; } - while (!dups.isEmpty()) { - // Grab a contiguous trailing sequence of dups. - int grab = dups.size() - 1; - int dupArgPos = dups.get(grab), dupArgCount = 1; - while (grab - 1 >= 0) { - int dup0 = dups.get(grab - 1); - if (dup0 != dupArgPos - 1) break; - dupArgPos -= 1; - dupArgCount += 1; - grab -= 1; - } - //if (dupArgCount > 1) System.out.println("Dup: "+dups.subList(grab, dups.size())); - dups.subList(grab, dups.size()).clear(); - // In the new target type drop that many args from the tail: - List> ptypes = oldType.parameterList(); - ptypes = ptypes.subList(0, ptypes.size() - dupArgCount); - MethodType dupType = MethodType.methodType(oldType.returnType(), ptypes); - target = AdapterMethodHandle.makeDupArguments(dupType, target, dupArgPos, dupArgCount); - if (target == null) - throw newIllegalArgumentException("cannot dup"); - oldType = target.type(); + //if (dropArgCount > 1) System.out.println("Drop: "+drops.subList(0, dropArgCount)); + drops.subList(0, dropArgCount).clear(); + List> dropTypes = newType.parameterList() + .subList(dropArgPos, dropArgPos + dropArgCount); + MethodType dropType = oldType.insertParameterTypes(dropArgPos, dropTypes); + target = AdapterMethodHandle.makeDropArguments(dropType, target, dropArgPos, dropArgCount); + if (target == null) throw newIllegalArgumentException("cannot drop"); + oldType = target.type(); + } + return convertArguments(target, newType, oldType, 0); + } + + /*non-public*/ static + MethodHandle convertArguments(MethodHandle target, MethodType newType, int level) { + MethodType oldType = target.type(); + if (oldType.equals(newType)) + return target; + assert(level > 1 || oldType.isConvertibleTo(newType)); + MethodHandle retFilter = null; + Class oldRT = oldType.returnType(); + Class newRT = newType.returnType(); + if (!VerifyType.isNullConversion(oldRT, newRT)) { + if (oldRT == void.class) { + Wrapper wrap = newRT.isPrimitive() ? Wrapper.forPrimitiveType(newRT) : Wrapper.OBJECT; + retFilter = ValueConversions.zeroConstantFunction(wrap); + } else { + retFilter = MethodHandles.identity(newRT); + retFilter = convertArguments(retFilter, retFilter.type().changeParameterType(0, oldRT), level); } - while (!drops.isEmpty()) { - // Grab a contiguous initial sequence of drops. - int dropArgPos = drops.get(0), dropArgCount = 1; - while (dropArgCount < drops.size()) { - int drop1 = drops.get(dropArgCount); - if (drop1 != dropArgPos + dropArgCount) break; - dropArgCount += 1; - } - //if (dropArgCount > 1) System.out.println("Drop: "+drops.subList(0, dropArgCount)); - drops.subList(0, dropArgCount).clear(); - List> dropTypes = newType.parameterList() - .subList(dropArgPos, dropArgPos + dropArgCount); - MethodType dropType = oldType.insertParameterTypes(dropArgPos, dropTypes); - target = AdapterMethodHandle.makeDropArguments(dropType, target, dropArgPos, dropArgCount); - if (target == null) throw newIllegalArgumentException("cannot drop"); - oldType = target.type(); - } + newType = newType.changeReturnType(oldRT); + } + MethodHandle res = null; + Exception ex = null; + try { + res = convertArguments(target, newType, oldType, level); + } catch (IllegalArgumentException ex1) { + ex = ex1; } + if (res == null) { + WrongMethodTypeException wmt = new WrongMethodTypeException("cannot convert to "+newType+": "+target); + wmt.initCause(ex); + throw wmt; + } + if (retFilter != null) + res = MethodHandles.filterReturnValue(res, retFilter); + return res; + } + + static MethodHandle convertArguments(MethodHandle target, + MethodType newType, + MethodType oldType, + int level) { + assert(oldType.parameterCount() == target.type().parameterCount()); if (newType == oldType) return target; if (oldType.parameterCount() != newType.parameterCount()) - throw newIllegalArgumentException("mismatched parameter count"); - MethodHandle res = AdapterMethodHandle.makePairwiseConvert(newType, target); + throw newIllegalArgumentException("mismatched parameter count", oldType, newType); + MethodHandle res = AdapterMethodHandle.makePairwiseConvert(newType, target, level); if (res != null) return res; + // We can come here in the case of target(int)void => (Object)void, + // because the unboxing logic for Object => int is complex. int argc = oldType.parameterCount(); + assert(MethodHandleNatives.workaroundWithoutRicochetFrames()); // this code is deprecated // The JVM can't do it directly, so fill in the gap with a Java adapter. // TO DO: figure out what to put here from case-by-case experience // Use a heavier method: Convert all the arguments to Object, // then back to the desired types. We might have to use Java-based // method handles to do this. MethodType objType = MethodType.genericMethodType(argc); - MethodHandle objTarget = AdapterMethodHandle.makePairwiseConvert(objType, target); + MethodHandle objTarget = AdapterMethodHandle.makePairwiseConvert(objType, target, level); if (objTarget == null) objTarget = FromGeneric.make(target); - res = AdapterMethodHandle.makePairwiseConvert(newType, objTarget); + res = AdapterMethodHandle.makePairwiseConvert(newType, objTarget, level); if (res != null) return res; return ToGeneric.make(newType, objTarget); } + static MethodHandle spreadArguments(MethodHandle target, Class arrayType, int arrayLength) { + MethodType oldType = target.type(); + int nargs = oldType.parameterCount(); + int keepPosArgs = nargs - arrayLength; + MethodType newType = oldType + .dropParameterTypes(keepPosArgs, nargs) + .insertParameterTypes(keepPosArgs, arrayType); + return spreadArguments(target, newType, keepPosArgs, arrayType, arrayLength); + } + static MethodHandle spreadArguments(MethodHandle target, MethodType newType, int spreadArgPos) { + int arrayLength = target.type().parameterCount() - spreadArgPos; + return spreadArguments(target, newType, spreadArgPos, Object[].class, arrayLength); + } static MethodHandle spreadArguments(MethodHandle target, MethodType newType, - int spreadArg) { + int spreadArgPos, + Class arrayType, + int arrayLength) { // TO DO: maybe allow the restarg to be Object and implicitly cast to Object[] MethodType oldType = target.type(); // spread the last argument of newType to oldType - int spreadCount = oldType.parameterCount() - spreadArg; - Class spreadArgType = Object[].class; - MethodHandle res = AdapterMethodHandle.makeSpreadArguments(newType, target, spreadArgType, spreadArg, spreadCount); - if (res != null) - return res; - // try an intermediate adapter - Class spreadType = null; - if (spreadArg < 0 || spreadArg >= newType.parameterCount() - || !VerifyType.isSpreadArgType(spreadType = newType.parameterType(spreadArg))) - throw newIllegalArgumentException("no restarg in "+newType); - Class[] ptypes = oldType.parameterArray(); - for (int i = 0; i < spreadCount; i++) - ptypes[spreadArg + i] = VerifyType.spreadArgElementType(spreadType, i); - MethodType midType = MethodType.methodType(newType.returnType(), ptypes); - // after spreading, some arguments may need further conversion - MethodHandle target2 = convertArguments(target, midType, oldType, null); - if (target2 == null) - throw new UnsupportedOperationException("NYI: convert "+midType+" =calls=> "+oldType); - res = AdapterMethodHandle.makeSpreadArguments(newType, target2, spreadArgType, spreadArg, spreadCount); - if (res != null) - return res; - res = SpreadGeneric.make(target2, spreadCount); - if (res != null) - res = convertArguments(res, newType, res.type(), null); + assert(arrayLength == oldType.parameterCount() - spreadArgPos); + assert(newType.parameterType(spreadArgPos) == arrayType); + MethodHandle res = AdapterMethodHandle.makeSpreadArguments(newType, target, arrayType, spreadArgPos, arrayLength); + if (res == null) throw new IllegalArgumentException("spread on "+target+" with "+arrayType.getSimpleName()); return res; } static MethodHandle collectArguments(MethodHandle target, + int collectArg, + MethodHandle collector) { + MethodType type = target.type(); + Class collectType = collector.type().returnType(); + if (collectType != type.parameterType(collectArg)) + target = target.asType(type.changeParameterType(collectArg, collectType)); + MethodType newType = type + .dropParameterTypes(collectArg, collectArg+1) + .insertParameterTypes(collectArg, collector.type().parameterArray()); + return collectArguments(target, newType, collectArg, collector); + } + static MethodHandle collectArguments(MethodHandle target, MethodType newType, int collectArg, MethodHandle collector) { MethodType oldType = target.type(); // (a...,c)=>r - if (collector == null) { - int numCollect = newType.parameterCount() - oldType.parameterCount() + 1; - collector = ValueConversions.varargsArray(numCollect); - } // newType // (a..., b...)=>r MethodType colType = collector.type(); // (b...)=>c // oldType // (a..., b...)=>r assert(newType.parameterCount() == collectArg + colType.parameterCount()); assert(oldType.parameterCount() == collectArg + 1); - MethodHandle gtarget = convertArguments(target, oldType.generic(), oldType, null); - MethodHandle gcollector = convertArguments(collector, colType.generic(), colType, null); - if (gtarget == null || gcollector == null) return null; - MethodHandle gresult = FilterGeneric.makeArgumentCollector(gcollector, gtarget); - MethodHandle result = convertArguments(gresult, newType, gresult.type(), null); + MethodHandle result = null; + if (AdapterMethodHandle.canCollectArguments(oldType, colType, collectArg, false)) { + result = AdapterMethodHandle.makeCollectArguments(target, collector, collectArg, false); + } + if (result == null) { + assert(MethodHandleNatives.workaroundWithoutRicochetFrames()); // this code is deprecated + MethodHandle gtarget = convertArguments(target, oldType.generic(), oldType, 0); + MethodHandle gcollector = convertArguments(collector, colType.generic(), colType, 0); + if (gtarget == null || gcollector == null) return null; + MethodHandle gresult = FilterGeneric.makeArgumentCollector(gcollector, gtarget); + result = convertArguments(gresult, newType, gresult.type(), 0); + } return result; } static MethodHandle filterArgument(MethodHandle target, - int pos, - MethodHandle filter) { - MethodType ttype = target.type(), gttype = ttype.generic(); + int pos, + MethodHandle filter) { + MethodType ttype = target.type(); + MethodType ftype = filter.type(); + assert(ftype.parameterCount() == 1); + MethodType rtype = ttype.changeParameterType(pos, ftype.parameterType(0)); + MethodType gttype = ttype.generic(); if (ttype != gttype) { - target = convertArguments(target, gttype, ttype, null); + target = convertArguments(target, gttype, ttype, 0); ttype = gttype; } - MethodType ftype = filter.type(), gftype = ftype.generic(); - if (ftype.parameterCount() != 1) - throw new InternalError(); + MethodType gftype = ftype.generic(); if (ftype != gftype) { - filter = convertArguments(filter, gftype, ftype, null); + filter = convertArguments(filter, gftype, ftype, 0); ftype = gftype; } - if (ftype == ttype) { + MethodHandle result = null; + if (AdapterMethodHandle.canCollectArguments(ttype, ftype, pos, false)) { + result = AdapterMethodHandle.makeCollectArguments(target, filter, pos, false); + } + if (result == null) { + assert(MethodHandleNatives.workaroundWithoutRicochetFrames()); // this code is deprecated + if (ftype == ttype) { // simple unary case - return FilterOneArgument.make(filter, target); + result = FilterOneArgument.make(filter, target); + } else { + result = FilterGeneric.makeArgumentFilter(pos, filter, target); + } } - return FilterGeneric.makeArgumentFilter(pos, filter, target); + if (result.type() != rtype) + result = result.asType(rtype); + return result; } static MethodHandle foldArguments(MethodHandle target, - MethodType newType, - MethodHandle combiner) { + MethodType newType, + int foldPos, + MethodHandle combiner) { MethodType oldType = target.type(); MethodType ctype = combiner.type(); - MethodHandle gtarget = convertArguments(target, oldType.generic(), oldType, null); - MethodHandle gcombiner = convertArguments(combiner, ctype.generic(), ctype, null); + if (AdapterMethodHandle.canCollectArguments(oldType, ctype, foldPos, true)) { + MethodHandle res = AdapterMethodHandle.makeCollectArguments(target, combiner, foldPos, true); + if (res != null) return res; + } + assert(MethodHandleNatives.workaroundWithoutRicochetFrames()); // this code is deprecated + if (foldPos != 0) return null; + MethodHandle gtarget = convertArguments(target, oldType.generic(), oldType, 0); + MethodHandle gcombiner = convertArguments(combiner, ctype.generic(), ctype, 0); + if (ctype.returnType() == void.class) { + gtarget = dropArguments(gtarget, oldType.generic().insertParameterTypes(foldPos, Object.class), foldPos); + } if (gtarget == null || gcombiner == null) return null; MethodHandle gresult = FilterGeneric.makeArgumentFolder(gcombiner, gtarget); - MethodHandle result = convertArguments(gresult, newType, gresult.type(), null); - return result; + return convertArguments(gresult, newType, gresult.type(), 0); } static @@ -802,6 +879,7 @@ this.target = target; this.fallback = fallback; } + // FIXME: Build the control flow out of foldArguments. static MethodHandle make(MethodHandle test, MethodHandle target, MethodHandle fallback) { MethodType type = target.type(); int nargs = type.parameterCount(); @@ -809,12 +887,12 @@ MethodHandle invoke = INVOKES[nargs]; MethodType gtype = type.generic(); assert(invoke.type().dropParameterTypes(0,1) == gtype); - MethodHandle gtest = convertArguments(test, gtype.changeReturnType(boolean.class), test.type(), null); - MethodHandle gtarget = convertArguments(target, gtype, type, null); - MethodHandle gfallback = convertArguments(fallback, gtype, type, null); + MethodHandle gtest = convertArguments(test, gtype.changeReturnType(boolean.class), test.type(), 0); + MethodHandle gtarget = convertArguments(target, gtype, type, 0); + MethodHandle gfallback = convertArguments(fallback, gtype, type, 0); if (gtest == null || gtarget == null || gfallback == null) return null; MethodHandle gguard = new GuardWithTest(invoke, gtest, gtarget, gfallback); - return convertArguments(gguard, type, gtype, null); + return convertArguments(gguard, type, gtype, 0); } else { MethodHandle invoke = VARARGS_INVOKE; MethodType gtype = MethodType.genericMethodType(1); @@ -925,8 +1003,9 @@ GuardWithCatch(MethodHandle target, Class exType, MethodHandle catcher) { this(INVOKES[target.type().parameterCount()], target, exType, catcher); } - GuardWithCatch(MethodHandle invoker, - MethodHandle target, Class exType, MethodHandle catcher) { + // FIXME: Build the control flow out of foldArguments. + GuardWithCatch(MethodHandle invoker, + MethodHandle target, Class exType, MethodHandle catcher) { super(invoker); this.target = target; this.exType = exType; @@ -1057,11 +1136,11 @@ if (nargs < GuardWithCatch.INVOKES.length) { MethodType gtype = type.generic(); MethodType gcatchType = gtype.insertParameterTypes(0, Throwable.class); - MethodHandle gtarget = convertArguments(target, gtype, type, null); - MethodHandle gcatcher = convertArguments(catcher, gcatchType, ctype, null); + MethodHandle gtarget = convertArguments(target, gtype, type, 0); + MethodHandle gcatcher = convertArguments(catcher, gcatchType, ctype, 0); MethodHandle gguard = new GuardWithCatch(gtarget, exType, gcatcher); if (gtarget == null || gcatcher == null || gguard == null) return null; - return convertArguments(gguard, type, gtype, null); + return convertArguments(gguard, type, gtype, 0); } else { MethodType gtype = MethodType.genericMethodType(0, true); MethodType gcatchType = gtype.insertParameterTypes(0, Throwable.class); diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java --- a/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java Tue May 17 14:29:59 2011 -0700 @@ -115,6 +115,8 @@ /** Which conv-ops are implemented by the JVM? */ static final int CONV_OP_IMPLEMENTED_MASK; + /** Derived mode flag. Only false on some old JVM implementations. */ + static final boolean HAVE_RICOCHET_FRAMES; private static native void registerNatives(); static { @@ -141,6 +143,7 @@ if (CONV_OP_IMPLEMENTED_MASK_ == 0) CONV_OP_IMPLEMENTED_MASK_ = DEFAULT_CONV_OP_IMPLEMENTED_MASK; CONV_OP_IMPLEMENTED_MASK = CONV_OP_IMPLEMENTED_MASK_; + HAVE_RICOCHET_FRAMES = (CONV_OP_IMPLEMENTED_MASK & (1<int, Object->T) OP_CHECK_CAST = 0x2, // ref-to-ref conversion; requires a Class argument OP_PRIM_TO_PRIM = 0x3, // converts from one primitive to another OP_REF_TO_PRIM = 0x4, // unboxes a wrapper to produce a primitive - OP_PRIM_TO_REF = 0x5, // boxes a primitive into a wrapper (NYI) + OP_PRIM_TO_REF = 0x5, // boxes a primitive into a wrapper OP_SWAP_ARGS = 0x6, // swap arguments (vminfo is 2nd arg) OP_ROT_ARGS = 0x7, // rotate arguments (vminfo is displaced arg) OP_DUP_ARGS = 0x8, // duplicates one or more arguments (at TOS) OP_DROP_ARGS = 0x9, // remove one or more argument slots - OP_COLLECT_ARGS = 0xA, // combine one or more arguments into a varargs (NYI) + OP_COLLECT_ARGS = 0xA, // combine arguments using an auxiliary function OP_SPREAD_ARGS = 0xB, // expand in place a varargs array (of known size) - OP_FLYBY = 0xC, // operate first on reified argument list (NYI) - OP_RICOCHET = 0xD, // run an adapter chain on the return value (NYI) + OP_FOLD_ARGS = 0xC, // combine but do not remove arguments; prepend result + //OP_UNUSED_13 = 0xD, // unused code, perhaps for reified argument lists CONV_OP_LIMIT = 0xE; // limit of CONV_OP enumeration /** Shift and mask values for decoding the AMH.conversion field. * These numbers are shared with the JVM for creating AMHs. */ static final int CONV_OP_MASK = 0xF00, // this nybble contains the conversion op field + CONV_TYPE_MASK = 0x0F, // fits T_ADDRESS and below CONV_VMINFO_MASK = 0x0FF, // LSB is reserved for JVM use CONV_VMINFO_SHIFT = 0, // position of bits in CONV_VMINFO_MASK CONV_OP_SHIFT = 8, // position of bits in CONV_OP_MASK @@ -244,8 +248,9 @@ T_LONG = 11, T_OBJECT = 12, //T_ARRAY = 13 - T_VOID = 14; + T_VOID = 14, //T_ADDRESS = 15 + T_ILLEGAL = 99; /** * Constant pool reference-kind codes, as used by CONSTANT_MethodHandle CP entries. @@ -273,16 +278,29 @@ try { Field con = Constants.class.getDeclaredField(name); int jval = con.getInt(null); - if (jval != vmval) - throw new InternalError(name+": JVM has "+vmval+" while Java has "+jval); + if (jval == vmval) continue; + String err = (name+": JVM has "+vmval+" while Java has "+jval); + if (name.equals("CONV_OP_LIMIT")) { + System.err.println("warning: "+err); + continue; + } + throw new InternalError(err); } catch (Exception ex) { + if (ex instanceof NoSuchFieldException) { + String err = (name+": JVM has "+vmval+" which Java does not define"); + // ignore exotic ops the JVM cares about; we just wont issue them + if (name.startsWith("OP_") || name.startsWith("GC_")) { + System.err.println("warning: "+err); + continue; + } + } throw new InternalError(name+": access failed, got "+ex); } } return true; } static { - verifyConstants(); + assert(verifyConstants()); } // Up-calls from the JVM. @@ -313,7 +331,7 @@ } /** - * The JVM wants to use a MethodType with invokeGeneric. Give the runtime fair warning. + * The JVM wants to use a MethodType with inexact invoke. Give the runtime fair warning. */ static void notifyGenericMethodType(MethodType type) { type.form().notifyGenericMethodType(); @@ -323,15 +341,39 @@ * The JVM wants to raise an exception. Here's the path. */ static void raiseException(int code, Object actual, Object required) { - String message; + String message = null; + switch (code) { + case 190: // arraylength + try { + String reqLength = ""; + if (required instanceof AdapterMethodHandle) { + int conv = ((AdapterMethodHandle)required).getConversion(); + int spChange = AdapterMethodHandle.extractStackMove(conv); + reqLength = " of length "+(spChange+1); + } + int actualLength = actual == null ? 0 : java.lang.reflect.Array.getLength(actual); + message = "required array"+reqLength+", but encountered wrong length "+actualLength; + break; + } catch (IllegalArgumentException ex) { + } + required = Object[].class; // should have been an array + code = 192; // checkcast + break; + } // disregard the identity of the actual object, if it is not a class: - if (!(actual instanceof Class) && !(actual instanceof MethodType)) - actual = actual.getClass(); - if (actual != null) - message = "required "+required+" but encountered "+actual; - else - message = "required "+required; + if (message == null) { + if (!(actual instanceof Class) && !(actual instanceof MethodType)) + actual = actual.getClass(); + if (actual != null) + message = "required "+required+" but encountered "+actual; + else + message = "required "+required; + } switch (code) { + case 190: // arraylength + throw new ArrayIndexOutOfBoundsException(message); + case 50: //_aaload + throw new ClassCastException(message); case 192: // checkcast throw new ClassCastException(message); default: @@ -365,4 +407,13 @@ throw err; } } + + /** + * This assertion marks code which was written before ricochet frames were implemented. + * Such code will go away when the ports catch up. + */ + static boolean workaroundWithoutRicochetFrames() { + assert(!HAVE_RICOCHET_FRAMES) : "this code should not be executed if `-XX:+UseRicochetFrames is enabled"; + return true; + } } diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/MethodHandleStatics.java --- a/jdk/src/share/classes/java/lang/invoke/MethodHandleStatics.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleStatics.java Tue May 17 14:29:59 2011 -0700 @@ -63,8 +63,17 @@ } static void checkSpreadArgument(Object av, int n) { - if (av == null ? n != 0 : ((Object[])av).length != n) - throw newIllegalArgumentException("Array is not of length "+n); + if (av == null) { + if (n == 0) return; + } else if (av instanceof Object[]) { + int len = ((Object[])av).length; + if (len == n) return; + } else { + int len = java.lang.reflect.Array.getLength(av); + if (len == n) return; + } + // fall through to error: + throw newIllegalArgumentException("Array is not of length "+n); } // handy shared exception makers (they simplify the common case code) @@ -80,6 +89,9 @@ /*non-public*/ static RuntimeException newIllegalArgumentException(String message, Object obj) { return new IllegalArgumentException(message(message, obj)); } + /*non-public*/ static RuntimeException newIllegalArgumentException(String message, Object obj, Object obj2) { + return new IllegalArgumentException(message(message, obj, obj2)); + } /*non-public*/ static Error uncaughtException(Exception ex) { Error err = new InternalError("uncaught exception"); err.initCause(ex); @@ -89,4 +101,8 @@ if (obj != null) message = message + ": " + obj; return message; } + private static String message(String message, Object obj, Object obj2) { + if (obj != null || obj2 != null) message = message + ": " + obj + ", " + obj2; + return message; + } } diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/MethodHandles.java --- a/jdk/src/share/classes/java/lang/invoke/MethodHandles.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandles.java Tue May 17 14:29:59 2011 -0700 @@ -190,7 +190,7 @@ * is not symbolically accessible from the lookup class's loader, * the lookup can still succeed. * For example, lookups for {@code MethodHandle.invokeExact} and - * {@code MethodHandle.invokeGeneric} will always succeed, regardless of requested type. + * {@code MethodHandle.invoke} will always succeed, regardless of requested type. *
  • If there is a security manager installed, it can forbid the lookup * on various grounds (see below). * By contrast, the {@code ldc} instruction is not subject to @@ -590,10 +590,10 @@ * Because of the general equivalence between {@code invokevirtual} * instructions and method handles produced by {@code findVirtual}, * if the class is {@code MethodHandle} and the name string is - * {@code invokeExact} or {@code invokeGeneric}, the resulting + * {@code invokeExact} or {@code invoke}, the resulting * method handle is equivalent to one produced by * {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker} or - * {@link java.lang.invoke.MethodHandles#genericInvoker MethodHandles.genericInvoker} + * {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker} * with the same {@code type} argument. * * @param refc the class or interface from which the method is accessed @@ -1080,7 +1080,7 @@ MethodType rawType = mh.type(); if (rawType.parameterType(0) == caller) return mh; MethodType narrowType = rawType.changeParameterType(0, caller); - MethodHandle narrowMH = MethodHandleImpl.convertArguments(mh, narrowType, rawType, null); + MethodHandle narrowMH = MethodHandleImpl.convertArguments(mh, narrowType, rawType, 0); return fixVarargs(narrowMH, mh); } @@ -1148,7 +1148,7 @@ *
  • an {@code Object[]} array containing more arguments * *

    - * The invoker will behave like a call to {@link MethodHandle#invokeGeneric invokeGeneric} with + * The invoker will behave like a call to {@link MethodHandle#invoke invoke} with * the indicated {@code type}. * That is, if the target is exactly of the given {@code type}, it will behave * like {@code invokeExact}; otherwise it behave as if {@link MethodHandle#asType asType} @@ -1166,7 +1166,7 @@ *

    * This method is equivalent to the following code (though it may be more efficient): *

    -MethodHandle invoker = MethodHandles.genericInvoker(type);
    +MethodHandle invoker = MethodHandles.invoker(type);
     int spreadArgCount = type.parameterCount - objectArgCount;
     invoker = invoker.asSpreader(Object[].class, spreadArgCount);
     return invoker;
    @@ -1186,7 +1186,7 @@
     
         /**
          * Produces a special invoker method handle which can be used to
    -     * invoke any method handle of the given type, as if by {@code invokeExact}.
    +     * invoke any method handle of the given type, as if by {@link MethodHandle#invokeExact invokeExact}.
          * The resulting invoker will have a type which is
          * exactly equal to the desired type, except that it will accept
          * an additional leading argument of type {@code MethodHandle}.
    @@ -1203,7 +1203,7 @@
          * For example, to emulate an {@code invokeExact} call to a variable method
          * handle {@code M}, extract its type {@code T},
          * look up the invoker method {@code X} for {@code T},
    -     * and call the invoker method, as {@code X.invokeGeneric(T, A...)}.
    +     * and call the invoker method, as {@code X.invoke(T, A...)}.
          * (It would not work to call {@code X.invokeExact}, since the type {@code T}
          * is unknown.)
          * If spreading, collecting, or other argument transformations are required,
    @@ -1212,7 +1212,7 @@
          * 

    * (Note: The invoker method is not available via the Core Reflection API. * An attempt to call {@linkplain java.lang.reflect.Method#invoke Method.invoke} - * on the declared {@code invokeExact} or {@code invokeGeneric} method will raise an + * on the declared {@code invokeExact} or {@code invoke} method will raise an * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.) *

    * This method throws no reflective or security exceptions. @@ -1226,20 +1226,20 @@ /** * Produces a special invoker method handle which can be used to - * invoke any method handle of the given type, as if by {@code invokeGeneric}. + * invoke any method handle compatible with the given type, as if by {@link MethodHandle#invoke invoke}. * The resulting invoker will have a type which is * exactly equal to the desired type, except that it will accept * an additional leading argument of type {@code MethodHandle}. *

    * Before invoking its target, the invoker will apply reference casts as - * necessary and unbox and widen primitive arguments, as if by {@link #convertArguments convertArguments}. - * The return value of the invoker will be an {@code Object} reference, - * boxing a primitive value if the original type returns a primitive, - * and always null if the original type returns void. + * necessary and box, unbox, or widen primitive values, as if by {@link MethodHandle#asType asType}. + * Similarly, the return value will be converted as necessary. + * If the target is a {@linkplain MethodHandle#asVarargsCollector variable arity method handle}, + * the required arity conversion will be made, again as if by {@link MethodHandle#asType asType}. *

    * This method is equivalent to the following code (though it may be more efficient): *

    -publicLookup().findVirtual(MethodHandle.class, "invokeGeneric", type)
    +publicLookup().findVirtual(MethodHandle.class, "invoke", type)
          * 
    *

    * This method throws no reflective or security exceptions. @@ -1247,8 +1247,17 @@ * @return a method handle suitable for invoking any method handle convertible to the given type */ static public + MethodHandle invoker(MethodType type) { + return type.invokers().generalInvoker(); + } + + /** + * Temporary alias for {@link #invoker}, for backward compatibility with some versions of JSR 292. + * @deprecated Will be removed for JSR 292 Proposed Final Draft. + */ + public static MethodHandle genericInvoker(MethodType type) { - return type.invokers().genericInvoker(); + return invoker(type); } /** @@ -1368,18 +1377,7 @@ */ public static MethodHandle convertArguments(MethodHandle target, MethodType newType) { - MethodType oldType = target.type(); - if (oldType.equals(newType)) - return target; - MethodHandle res = null; - try { - res = MethodHandleImpl.convertArguments(target, - newType, oldType, null); - } catch (IllegalArgumentException ex) { - } - if (res == null) - throw new WrongMethodTypeException("cannot convert to "+newType+": "+target); - return res; + return MethodHandleImpl.convertArguments(target, newType, 1); } /** @@ -1422,7 +1420,7 @@ */ public static MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) { - return convertArguments(target, newType); // FIXME! + return MethodHandleImpl.convertArguments(target, newType, 2); } /* @@ -1517,23 +1515,32 @@ MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) { MethodType oldType = target.type(); checkReorder(reorder, newType, oldType); - return MethodHandleImpl.convertArguments(target, + return MethodHandleImpl.permuteArguments(target, newType, oldType, reorder); } private static void checkReorder(int[] reorder, MethodType newType, MethodType oldType) { + if (newType.returnType() != oldType.returnType()) + throw newIllegalArgumentException("return types do not match", + oldType, newType); if (reorder.length == oldType.parameterCount()) { int limit = newType.parameterCount(); boolean bad = false; - for (int i : reorder) { + for (int j = 0; j < reorder.length; j++) { + int i = reorder[j]; if (i < 0 || i >= limit) { bad = true; break; } + Class src = newType.parameterType(i); + Class dst = oldType.parameterType(j); + if (src != dst) + throw newIllegalArgumentException("parameter types do not match after reorder", + oldType, newType); } if (!bad) return; } - throw newIllegalArgumentException("bad reorder array"); + throw newIllegalArgumentException("bad reorder array: "+Arrays.toString(reorder)); } /** @@ -1622,7 +1629,7 @@ if (type == void.class) throw newIllegalArgumentException("void type"); Wrapper w = Wrapper.forPrimitiveType(type); - return identity(type).bindTo(w.convert(value, type)); + return insertArguments(identity(type), 0, w.convert(value, type)); } else { return identity(type).bindTo(type.cast(value)); } @@ -1857,7 +1864,8 @@ MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) { MethodType targetType = target.type(); MethodHandle adapter = target; - MethodType adapterType = targetType; + MethodType adapterType = null; + assert((adapterType = targetType) != null); int maxPos = targetType.parameterCount(); if (pos + filters.length > maxPos) throw newIllegalArgumentException("too many filters"); @@ -1865,19 +1873,23 @@ for (MethodHandle filter : filters) { curPos += 1; if (filter == null) continue; // ignore null elements of filters - MethodType filterType = filter.type(); - if (filterType.parameterCount() != 1 - || filterType.returnType() != targetType.parameterType(curPos)) - throw newIllegalArgumentException("target and filter types do not match"); - adapterType = adapterType.changeParameterType(curPos, filterType.parameterType(0)); - adapter = MethodHandleImpl.filterArgument(adapter, curPos, filter); + adapter = filterArgument(adapter, curPos, filter); + assert((adapterType = adapterType.changeParameterType(curPos, filter.type().parameterType(0))) != null); } - MethodType midType = adapter.type(); - if (midType != adapterType) - adapter = MethodHandleImpl.convertArguments(adapter, adapterType, midType, null); + assert(adapterType.equals(adapter.type())); return adapter; } + /*non-public*/ static + MethodHandle filterArgument(MethodHandle target, int pos, MethodHandle filter) { + MethodType targetType = target.type(); + MethodType filterType = filter.type(); + if (filterType.parameterCount() != 1 + || filterType.returnType() != targetType.parameterType(pos)) + throw newIllegalArgumentException("target and filter types do not match", targetType, filterType); + return MethodHandleImpl.filterArgument(target, pos, filter); + } + /** * Adapts a target method handle {@code target} by post-processing * its return value with a unary filter function. @@ -1913,14 +1925,26 @@ MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) { MethodType targetType = target.type(); MethodType filterType = filter.type(); - if (filterType.parameterCount() != 1 - || filterType.parameterType(0) != targetType.returnType()) - throw newIllegalArgumentException("target and filter types do not match"); + Class rtype = targetType.returnType(); + int filterValues = filterType.parameterCount(); + if (filterValues == 0 + ? (rtype != void.class) + : (rtype != filterType.parameterType(0))) + throw newIllegalArgumentException("target and filter types do not match", target, filter); // result = fold( lambda(retval, arg...) { filter(retval) }, // lambda( arg...) { target(arg...) } ) + MethodType newType = targetType.changeReturnType(filterType.returnType()); + MethodHandle result = null; + if (AdapterMethodHandle.canCollectArguments(filterType, targetType, 0, false)) { + result = AdapterMethodHandle.makeCollectArguments(filter, target, 0, false); + if (result != null) return result; + } // FIXME: Too many nodes here. - MethodHandle returner = dropArguments(filter, 1, targetType.parameterList()); - return foldArguments(returner, target); + assert(MethodHandleNatives.workaroundWithoutRicochetFrames()); // this class is deprecated + MethodHandle returner = dropArguments(filter, filterValues, targetType.parameterList()); + result = foldArguments(returner, target); + assert(result.type().equals(newType)); + return result; } /** @@ -1972,16 +1996,23 @@ MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) { MethodType targetType = target.type(); MethodType combinerType = combiner.type(); + int foldPos = 0; // always at the head, at present int foldArgs = combinerType.parameterCount(); - boolean ok = (targetType.parameterCount() >= 1 + foldArgs); - if (ok && !combinerType.parameterList().equals(targetType.parameterList().subList(1, foldArgs+1))) + int foldVals = combinerType.returnType() == void.class ? 0 : 1; + int afterInsertPos = foldPos + foldVals; + boolean ok = (targetType.parameterCount() >= afterInsertPos + foldArgs); + if (ok && !(combinerType.parameterList() + .equals(targetType.parameterList().subList(afterInsertPos, + afterInsertPos + foldArgs)))) ok = false; - if (ok && !combinerType.returnType().equals(targetType.parameterType(0))) + if (ok && foldVals != 0 && !combinerType.returnType().equals(targetType.parameterType(0))) ok = false; if (!ok) throw misMatchedTypes("target and combiner types", targetType, combinerType); - MethodType newType = targetType.dropParameterTypes(0, 1); - return MethodHandleImpl.foldArguments(target, newType, combiner); + MethodType newType = targetType.dropParameterTypes(foldPos, afterInsertPos); + MethodHandle res = MethodHandleImpl.foldArguments(target, newType, foldPos, combiner); + if (res == null) throw newIllegalArgumentException("cannot fold from "+newType+" to " +targetType); + return res; } /** @@ -2142,7 +2173,7 @@ * the given {@code target} on the incoming arguments, * and returning or throwing whatever the {@code target} * returns or throws. The invocation will be as if by - * {@code target.invokeGeneric}. + * {@code target.invoke}. * The target's type will be checked before the * instance is created, as if by a call to {@code asType}, * which may result in a {@code WrongMethodTypeException}. diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/MethodType.java --- a/jdk/src/share/classes/java/lang/invoke/MethodType.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/MethodType.java Tue May 17 14:29:59 2011 -0700 @@ -25,6 +25,7 @@ package java.lang.invoke; +import sun.invoke.util.Wrapper; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -39,7 +40,7 @@ * matched between a method handle and all its callers, * and the JVM's operations enforce this matching at, specifically * during calls to {@link MethodHandle#invokeExact MethodHandle.invokeExact} - * and {@link MethodHandle#invokeGeneric MethodHandle.invokeGeneric}, and during execution + * and {@link MethodHandle#invoke MethodHandle.invoke}, and during execution * of {@code invokedynamic} instructions. *

    * The structure is a return type accompanied by any number of parameter types. @@ -262,18 +263,18 @@ * Finds or creates a method type whose components are {@code Object} with an optional trailing {@code Object[]} array. * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. * All parameters and the return type will be {@code Object}, - * except the final varargs parameter if any, which will be {@code Object[]}. - * @param objectArgCount number of parameters (excluding the varargs parameter if any) - * @param varargs whether there will be a varargs parameter, of type {@code Object[]} - * @return a totally generic method type, given only its count of parameters and varargs - * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255 + * except the final array parameter if any, which will be {@code Object[]}. + * @param objectArgCount number of parameters (excluding the final array parameter if any) + * @param finalArray whether there will be a trailing array parameter, of type {@code Object[]} + * @return a generally applicable method type, for all calls of the given fixed argument count and a collected array of further arguments + * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255 (or 254, if {@code finalArray}) * @see #genericMethodType(int) */ public static - MethodType genericMethodType(int objectArgCount, boolean varargs) { + MethodType genericMethodType(int objectArgCount, boolean finalArray) { MethodType mt; checkSlotCount(objectArgCount); - int ivarargs = (!varargs ? 0 : 1); + int ivarargs = (!finalArray ? 0 : 1); int ootIndex = objectArgCount*2 + ivarargs; if (ootIndex < objectOnlyTypes.length) { mt = objectOnlyTypes[ootIndex]; @@ -294,7 +295,7 @@ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}. * All parameters and the return type will be Object. * @param objectArgCount number of parameters - * @return a totally generic method type, given only its count of parameters + * @return a generally applicable method type, for all calls of the given argument count * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255 * @see #genericMethodType(int, boolean) */ @@ -626,6 +627,30 @@ return sb.toString(); } + + /*non-public*/ + boolean isConvertibleTo(MethodType newType) { + if (!canConvert(returnType(), newType.returnType())) + return false; + int argc = parameterCount(); + if (argc != newType.parameterCount()) + return false; + for (int i = 0; i < argc; i++) { + if (!canConvert(newType.parameterType(i), parameterType(i))) + return false; + } + return true; + } + private static boolean canConvert(Class src, Class dst) { + if (src == dst || dst == void.class) return true; + if (src.isPrimitive() && dst.isPrimitive()) { + if (!Wrapper.forPrimitiveType(dst) + .isConvertibleFrom(Wrapper.forPrimitiveType(src))) + return false; + } + return true; + } + /// Queries which have to do with the bytecode architecture /** Reports the number of JVM stack slots required to invoke a method diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/MethodTypeForm.java --- a/jdk/src/share/classes/java/lang/invoke/MethodTypeForm.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/MethodTypeForm.java Tue May 17 14:29:59 2011 -0700 @@ -46,6 +46,7 @@ final long argCounts; // packed slot & value counts final long primCounts; // packed prim & double counts final int vmslots; // total number of parameter slots + private Object vmlayout; // vm-specific information for calls final MethodType erasedType; // the canonical erasure /*lazy*/ MethodType primsAsBoxes; // replace prims by wrappers @@ -59,7 +60,7 @@ /*lazy*/ FromGeneric fromGeneric; // convert cs. w/o prims to with /*lazy*/ SpreadGeneric[] spreadGeneric; // expand one argument to many /*lazy*/ FilterGeneric filterGeneric; // convert argument(s) on the fly - /*lazy*/ MethodHandle genericInvoker; // hook for invokeGeneric + /*lazy*/ MethodHandle genericInvoker; // hook for inexact invoke public MethodType erasedType() { return erasedType; @@ -460,9 +461,9 @@ if (genericInvoker != null) return; try { // Trigger adapter creation. - genericInvoker = InvokeGeneric.genericInvokerOf(erasedType); + genericInvoker = InvokeGeneric.generalInvokerOf(erasedType); } catch (Exception ex) { - Error err = new InternalError("Exception while resolving invokeGeneric"); + Error err = new InternalError("Exception while resolving inexact invoke"); err.initCause(ex); throw err; } diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/SpreadGeneric.java --- a/jdk/src/share/classes/java/lang/invoke/SpreadGeneric.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/SpreadGeneric.java Tue May 17 14:29:59 2011 -0700 @@ -66,6 +66,10 @@ this.entryPoint = ep[0]; } + static { + assert(MethodHandleNatives.workaroundWithoutRicochetFrames()); // this class is deprecated + } + /** From targetType remove the last spreadCount arguments, and instead * append a simple Object argument. */ diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/ToGeneric.java --- a/jdk/src/share/classes/java/lang/invoke/ToGeneric.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/ToGeneric.java Tue May 17 14:29:59 2011 -0700 @@ -96,7 +96,7 @@ ToGeneric va2 = ToGeneric.of(primsAtEnd); this.adapter = va2.adapter; if (true) throw new UnsupportedOperationException("NYI: primitive parameters must follow references; entryType = "+entryType); - this.entryPoint = MethodHandleImpl.convertArguments( + this.entryPoint = MethodHandleImpl.permuteArguments( va2.entryPoint, primsAtEnd, entryType, primsAtEndOrder); // example: for entryType of (int,Object,Object), the reordered // type is (Object,Object,int) and the order is {1,2,0}, @@ -128,7 +128,7 @@ assert(eptWithInts.parameterType(i) == int.class); MethodType nextType = midType.changeParameterType(i, int.class); rawEntryPoint = MethodHandleImpl.convertArguments( - rawEntryPoint, nextType, midType, null); + rawEntryPoint, nextType, midType, 0); midType = nextType; } } @@ -152,6 +152,10 @@ this.invoker = makeRawArgumentFilter(invoker0, rawEntryTypeInit, entryType); } + static { + assert(MethodHandleNatives.workaroundWithoutRicochetFrames()); // this class is deprecated + } + /** A generic argument list will be created by a call of type 'raw'. * The values need to be reboxed for to match 'cooked'. * Do this on the fly. @@ -171,7 +175,7 @@ invoker.type().generic(), invoker, 0, MethodHandle.class); if (filteredInvoker == null) throw new UnsupportedOperationException("NYI"); } - MethodHandle reboxer = ValueConversions.rebox(dst, false); + MethodHandle reboxer = ValueConversions.rebox(dst); filteredInvoker = FilterGeneric.makeArgumentFilter(1+i, reboxer, filteredInvoker); if (filteredInvoker == null) throw new InternalError(); } @@ -199,13 +203,13 @@ assert(!rret.isPrimitive()); if (rret == Object.class && !mustCast) return null; - return ValueConversions.cast(tret, false); + return ValueConversions.cast(tret); } else if (tret == rret) { - return ValueConversions.unbox(tret, false); + return ValueConversions.unbox(tret); } else { assert(rret.isPrimitive()); assert(tret == double.class ? rret == long.class : rret == int.class); - return ValueConversions.unboxRaw(tret, false); + return ValueConversions.unboxRaw(tret); } } @@ -311,7 +315,7 @@ } static Adapter buildAdapterFromBytecodes(MethodType entryPointType) { - throw new UnsupportedOperationException("NYI"); + throw new UnsupportedOperationException("NYI: "+entryPointType); } /** diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/java/lang/invoke/package-info.java --- a/jdk/src/share/classes/java/lang/invoke/package-info.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/java/lang/invoke/package-info.java Tue May 17 14:29:59 2011 -0700 @@ -185,7 +185,7 @@ * The method handle constant produced for such a method behaves as if * it were created by {@link java.lang.invoke.MethodHandle#asVarargsCollector asVarargsCollector}. * In other words, the constant method handle will exhibit variable arity, - * when invoked via {@code invokeGeneric}. + * when invoked via {@code MethodHandle.invoke}. * On the other hand, its behavior with respect to {@code invokeExact} will be the same * as if the {@code varargs} bit were not set. *

    @@ -243,7 +243,7 @@ *

  • optionally, one or more additional static arguments
  • * * The method handle is then applied to the other values as if by - * {@link java.lang.invoke.MethodHandle#invokeGeneric invokeGeneric}. + * {@link java.lang.invoke.MethodHandle#invoke MethodHandle.invoke}. * The returned result must be a {@link java.lang.invoke.CallSite CallSite} (or a subclass). * The type of the call site's target must be exactly equal to the type * derived from the dynamic call site's type descriptor and passed to @@ -251,7 +251,7 @@ * The call site then becomes permanently linked to the dynamic call site. *

    * As long as each bootstrap method can be correctly invoked - * by invokeGeneric, its detailed type is arbitrary. + * by MethodHandle.invoke, its detailed type is arbitrary. * For example, the first argument could be {@code Object} * instead of {@code MethodHandles.Lookup}, and the return type * could also be {@code Object} instead of {@code CallSite}. @@ -272,7 +272,7 @@ * (i.e., a {@code CONSTANT_Class}, {@code CONSTANT_MethodType}, * or {@code CONSTANT_MethodHandle} argument cannot be linked)

  • *
  • the bootstrap method has the wrong arity, - * causing {@code invokeGeneric} to throw {@code WrongMethodTypeException}
  • + * causing {@code MethodHandle.invoke} to throw {@code WrongMethodTypeException} *
  • the bootstrap method has a wrong argument or return type
  • *
  • the bootstrap method invocation completes abnormally
  • *
  • the result from the bootstrap invocation is not a reference to @@ -381,10 +381,10 @@ * those values will be passed as additional arguments to the method handle. * (Note that because there is a limit of 255 arguments to any method, * at most 252 extra arguments can be supplied.) - * The bootstrap method will be invoked as if by either {@code invokeGeneric} + * The bootstrap method will be invoked as if by either {@code MethodHandle.invoke} * or {@code invokeWithArguments}. (There is no way to tell the difference.) *

    - * The normal argument conversion rules for {@code invokeGeneric} apply to all stacked arguments. + * The normal argument conversion rules for {@code MethodHandle.invoke} apply to all stacked arguments. * For example, if a pushed value is a primitive type, it may be converted to a reference by boxing conversion. * If the bootstrap method is a variable arity method (its modifier bit {@code 0x0080} is set), * then some or all of the arguments specified here may be collected into a trailing array parameter. @@ -419,8 +419,8 @@ * For example, the fourth argument could be {@code MethodHandle}, * if that is the type of the corresponding constant in * the {@code CONSTANT_InvokeDynamic} entry. - * In that case, the {@code invokeGeneric} call will pass the extra method handle - * constant as an {@code Object}, but the type matching machinery of {@code invokeGeneric} + * In that case, the {@code MethodHandle.invoke} call will pass the extra method handle + * constant as an {@code Object}, but the type matching machinery of {@code MethodHandle.invoke} * will cast the reference back to {@code MethodHandle} before invoking the bootstrap method. * (If a string constant were passed instead, by badly generated code, that cast would then fail, * resulting in a {@code BootstrapMethodError}.) diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/sun/invoke/util/ValueConversions.java --- a/jdk/src/share/classes/sun/invoke/util/ValueConversions.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/sun/invoke/util/ValueConversions.java Tue May 17 14:29:59 2011 -0700 @@ -31,10 +31,15 @@ import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.EnumMap; import java.util.List; public class ValueConversions { + private static final Class THIS_CLASS = ValueConversions.class; + // Do not adjust this except for special platforms: + private static final int MAX_ARITY = Integer.getInteger(THIS_CLASS.getName()+".MAX_ARITY", 255); + private static final Lookup IMPL_LOOKUP = MethodHandles.lookup(); private static EnumMap[] newWrapperCaches(int n) { @@ -42,88 +47,101 @@ EnumMap[] caches = (EnumMap[]) new EnumMap[n]; // unchecked warning expected here for (int i = 0; i < n; i++) - caches[i] = new EnumMap(Wrapper.class); + caches[i] = new EnumMap<>(Wrapper.class); return caches; } /// Converting references to values. - static int unboxInteger(Object x) { - if (x == null) return 0; // never NPE - return ((Integer) x).intValue(); + // There are several levels of this unboxing conversions: + // no conversions: exactly Integer.valueOf, etc. + // implicit conversions sanctioned by JLS 5.1.2, etc. + // explicit conversions as allowed by explicitCastArguments + + static int unboxInteger(Object x, boolean cast) { + if (x instanceof Integer) + return ((Integer) x).intValue(); + return primitiveConversion(Wrapper.INT, x, cast).intValue(); } - static byte unboxByte(Object x) { - if (x == null) return 0; // never NPE - return ((Byte) x).byteValue(); + static byte unboxByte(Object x, boolean cast) { + if (x instanceof Byte) + return ((Byte) x).byteValue(); + return primitiveConversion(Wrapper.BYTE, x, cast).byteValue(); } - static short unboxShort(Object x) { - if (x == null) return 0; // never NPE - return ((Short) x).shortValue(); + static short unboxShort(Object x, boolean cast) { + if (x instanceof Short) + return ((Short) x).shortValue(); + return primitiveConversion(Wrapper.SHORT, x, cast).shortValue(); } - static boolean unboxBoolean(Object x) { - if (x == null) return false; // never NPE - return ((Boolean) x).booleanValue(); + static boolean unboxBoolean(Object x, boolean cast) { + if (x instanceof Boolean) + return ((Boolean) x).booleanValue(); + return (primitiveConversion(Wrapper.BOOLEAN, x, cast).intValue() & 1) != 0; } - static char unboxCharacter(Object x) { - if (x == null) return 0; // never NPE - return ((Character) x).charValue(); + static char unboxCharacter(Object x, boolean cast) { + if (x instanceof Character) + return ((Character) x).charValue(); + return (char) primitiveConversion(Wrapper.CHAR, x, cast).intValue(); } - static long unboxLong(Object x) { - if (x == null) return 0; // never NPE - return ((Long) x).longValue(); + static long unboxLong(Object x, boolean cast) { + if (x instanceof Long) + return ((Long) x).longValue(); + return primitiveConversion(Wrapper.LONG, x, cast).longValue(); } - static float unboxFloat(Object x) { - if (x == null) return 0; // never NPE - return ((Float) x).floatValue(); + static float unboxFloat(Object x, boolean cast) { + if (x instanceof Float) + return ((Float) x).floatValue(); + return primitiveConversion(Wrapper.FLOAT, x, cast).floatValue(); } - static double unboxDouble(Object x) { - if (x == null) return 0; // never NPE - return ((Double) x).doubleValue(); + static double unboxDouble(Object x, boolean cast) { + if (x instanceof Double) + return ((Double) x).doubleValue(); + return primitiveConversion(Wrapper.DOUBLE, x, cast).doubleValue(); } /// Converting references to "raw" values. /// A raw primitive value is always an int or long. - static int unboxByteRaw(Object x) { - return unboxByte(x); + static int unboxByteRaw(Object x, boolean cast) { + return unboxByte(x, cast); } - static int unboxShortRaw(Object x) { - return unboxShort(x); + static int unboxShortRaw(Object x, boolean cast) { + return unboxShort(x, cast); } - static int unboxBooleanRaw(Object x) { - return unboxBoolean(x) ? 1 : 0; + static int unboxBooleanRaw(Object x, boolean cast) { + return unboxBoolean(x, cast) ? 1 : 0; } - static int unboxCharacterRaw(Object x) { - return unboxCharacter(x); + static int unboxCharacterRaw(Object x, boolean cast) { + return unboxCharacter(x, cast); } - static int unboxFloatRaw(Object x) { - return Float.floatToIntBits(unboxFloat(x)); + static int unboxFloatRaw(Object x, boolean cast) { + return Float.floatToIntBits(unboxFloat(x, cast)); } - static long unboxDoubleRaw(Object x) { - return Double.doubleToRawLongBits(unboxDouble(x)); + static long unboxDoubleRaw(Object x, boolean cast) { + return Double.doubleToRawLongBits(unboxDouble(x, cast)); } private static MethodType unboxType(Wrapper wrap, boolean raw) { - return MethodType.methodType(rawWrapper(wrap, raw).primitiveType(), wrap.wrapperType()); + return MethodType.methodType(rawWrapper(wrap, raw).primitiveType(), Object.class, boolean.class); } private static final EnumMap[] UNBOX_CONVERSIONS = newWrapperCaches(4); - private static MethodHandle unbox(Wrapper wrap, boolean exact, boolean raw) { - EnumMap cache = UNBOX_CONVERSIONS[(exact?1:0)+(raw?2:0)]; + private static MethodHandle unbox(Wrapper wrap, boolean raw, boolean cast) { + EnumMap cache = UNBOX_CONVERSIONS[(cast?1:0)+(raw?2:0)]; MethodHandle mh = cache.get(wrap); if (mh != null) { return mh; @@ -136,7 +154,7 @@ mh = raw ? ALWAYS_ZERO : IGNORE; break; case INT: case LONG: // these guys don't need separate raw channels - if (raw) mh = unbox(wrap, exact, false); + if (raw) mh = unbox(wrap, false, cast); break; } if (mh != null) { @@ -146,37 +164,62 @@ // look up the method String name = "unbox" + wrap.simpleName() + (raw ? "Raw" : ""); MethodType type = unboxType(wrap, raw); - if (!exact) { - try { - // actually, type is wrong; the Java method takes Object - mh = IMPL_LOOKUP.findStatic(ValueConversions.class, name, type.erase()); - } catch (ReflectiveOperationException ex) { - mh = null; - } - } else { - mh = unbox(wrap, !exact, raw).asType(type); + try { + mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); + } catch (ReflectiveOperationException ex) { + mh = null; } if (mh != null) { + mh = MethodHandles.insertArguments(mh, 1, cast); cache.put(wrap, mh); return mh; } - throw new IllegalArgumentException("cannot find unbox adapter for " + wrap + (raw ? " (raw)" : "")); + throw new IllegalArgumentException("cannot find unbox adapter for " + wrap + + (cast ? " (cast)" : "") + (raw ? " (raw)" : "")); + } + + public static MethodHandle unboxCast(Wrapper type) { + return unbox(type, false, true); } - public static MethodHandle unbox(Wrapper type, boolean exact) { - return unbox(type, exact, false); + public static MethodHandle unboxRaw(Wrapper type) { + return unbox(type, true, false); + } + + public static MethodHandle unbox(Class type) { + return unbox(Wrapper.forPrimitiveType(type), false, false); + } + + public static MethodHandle unboxCast(Class type) { + return unbox(Wrapper.forPrimitiveType(type), false, true); } - public static MethodHandle unboxRaw(Wrapper type, boolean exact) { - return unbox(type, exact, true); + public static MethodHandle unboxRaw(Class type) { + return unbox(Wrapper.forPrimitiveType(type), true, false); } - public static MethodHandle unbox(Class type, boolean exact) { - return unbox(Wrapper.forPrimitiveType(type), exact, false); - } - - public static MethodHandle unboxRaw(Class type, boolean exact) { - return unbox(Wrapper.forPrimitiveType(type), exact, true); + /// Primitive conversions + public static Number primitiveConversion(Wrapper wrap, Object x, boolean cast) { + // Maybe merge this code with Wrapper.convert/cast. + Number res = null; + if (x == null) { + if (!cast) return null; + x = wrap.zero(); + } + if (x instanceof Number) { + res = (Number) x; + } else if (x instanceof Boolean) { + res = ((boolean)x ? 1 : 0); + } else if (x instanceof Character) { + res = (int)(char)x; + } else { + // this will fail with the required ClassCastException: + res = (Number) x; + } + if (!cast && !wrap.isConvertibleFrom(Wrapper.forWrapperType(x.getClass()))) + // this will fail with the required ClassCastException: + res = (Number) wrap.wrapperType().cast(x); + return res; } /// Converting primitives to references @@ -285,7 +328,7 @@ MethodType type = boxType(wrap, raw); if (exact) { try { - mh = IMPL_LOOKUP.findStatic(ValueConversions.class, name, type); + mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); } catch (ReflectiveOperationException ex) { mh = null; } @@ -296,22 +339,31 @@ cache.put(wrap, mh); return mh; } - throw new IllegalArgumentException("cannot find box adapter for " + wrap + (raw ? " (raw)" : "")); + throw new IllegalArgumentException("cannot find box adapter for " + + wrap + (exact ? " (exact)" : "") + (raw ? " (raw)" : "")); } - public static MethodHandle box(Class type, boolean exact) { + public static MethodHandle box(Class type) { + boolean exact = false; + // e.g., boxShort(short)Short if exact, + // e.g., boxShort(short)Object if !exact return box(Wrapper.forPrimitiveType(type), exact, false); } - public static MethodHandle boxRaw(Class type, boolean exact) { + public static MethodHandle boxRaw(Class type) { + boolean exact = false; + // e.g., boxShortRaw(int)Short if exact + // e.g., boxShortRaw(int)Object if !exact return box(Wrapper.forPrimitiveType(type), exact, true); } - public static MethodHandle box(Wrapper type, boolean exact) { + public static MethodHandle box(Wrapper type) { + boolean exact = false; return box(type, exact, false); } - public static MethodHandle boxRaw(Wrapper type, boolean exact) { + public static MethodHandle boxRaw(Wrapper type) { + boolean exact = false; return box(type, exact, true); } @@ -319,16 +371,16 @@ static int unboxRawInteger(Object x) { if (x instanceof Integer) - return unboxInteger(x); + return (int) x; else - return (int) unboxLong(x); + return (int) unboxLong(x, false); } static Integer reboxRawInteger(Object x) { if (x instanceof Integer) return (Integer) x; else - return (int) unboxLong(x); + return (int) unboxLong(x, false); } static Byte reboxRawByte(Object x) { @@ -362,7 +414,7 @@ static Double reboxRawDouble(Object x) { if (x instanceof Double) return (Double) x; - return boxDoubleRaw(unboxLong(x)); + return boxDoubleRaw(unboxLong(x, true)); } private static MethodType reboxType(Wrapper wrap) { @@ -371,7 +423,7 @@ } private static final EnumMap[] - REBOX_CONVERSIONS = newWrapperCaches(2); + REBOX_CONVERSIONS = newWrapperCaches(1); /** * Because we normalize primitive types to reduce the number of signatures, @@ -380,10 +432,10 @@ * When the erased primitive value is then boxed into an Integer or Long, * the final boxed primitive is sometimes required. This transformation * is called a "rebox". It takes an Integer or Long and produces some - * other boxed value. + * other boxed value, typed (inexactly) as an Object */ - public static MethodHandle rebox(Wrapper wrap, boolean exact) { - EnumMap cache = REBOX_CONVERSIONS[exact?1:0]; + public static MethodHandle rebox(Wrapper wrap) { + EnumMap cache = REBOX_CONVERSIONS[0]; MethodHandle mh = cache.get(wrap); if (mh != null) { return mh; @@ -402,14 +454,11 @@ // look up the method String name = "reboxRaw" + wrap.simpleName(); MethodType type = reboxType(wrap); - if (exact) { - try { - mh = IMPL_LOOKUP.findStatic(ValueConversions.class, name, type); - } catch (ReflectiveOperationException ex) { - mh = null; - } - } else { - mh = rebox(wrap, !exact).asType(IDENTITY.type()); + try { + mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); + mh = mh.asType(IDENTITY.type()); + } catch (ReflectiveOperationException ex) { + mh = null; } if (mh != null) { cache.put(wrap, mh); @@ -418,8 +467,8 @@ throw new IllegalArgumentException("cannot find rebox adapter for " + wrap); } - public static MethodHandle rebox(Class type, boolean exact) { - return rebox(Wrapper.forPrimitiveType(type), exact); + public static MethodHandle rebox(Class type) { + return rebox(Wrapper.forPrimitiveType(type)); } /// Width-changing conversions between int and long. @@ -486,9 +535,10 @@ case VOID: mh = EMPTY; break; + case OBJECT: case INT: case LONG: case FLOAT: case DOUBLE: try { - mh = IMPL_LOOKUP.findStatic(ValueConversions.class, "zero"+wrap.simpleName(), type); + mh = IMPL_LOOKUP.findStatic(THIS_CLASS, "zero"+wrap.simpleName(), type); } catch (ReflectiveOperationException ex) { mh = null; } @@ -592,7 +642,7 @@ return t.cast(x); } - private static final MethodHandle IDENTITY, IDENTITY_I, IDENTITY_J, CAST_REFERENCE, ALWAYS_NULL, ALWAYS_ZERO, ZERO_OBJECT, IGNORE, EMPTY; + private static final MethodHandle IDENTITY, IDENTITY_I, IDENTITY_J, CAST_REFERENCE, ALWAYS_NULL, ALWAYS_ZERO, ZERO_OBJECT, IGNORE, EMPTY, NEW_ARRAY; static { try { MethodType idType = MethodType.genericMethodType(1); @@ -600,40 +650,56 @@ MethodType alwaysZeroType = idType.changeReturnType(int.class); MethodType ignoreType = idType.changeReturnType(void.class); MethodType zeroObjectType = MethodType.genericMethodType(0); - IDENTITY = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", idType); - IDENTITY_I = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", MethodType.methodType(int.class, int.class)); - IDENTITY_J = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", MethodType.methodType(long.class, long.class)); + IDENTITY = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", idType); + IDENTITY_I = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", MethodType.methodType(int.class, int.class)); + IDENTITY_J = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", MethodType.methodType(long.class, long.class)); //CAST_REFERENCE = IMPL_LOOKUP.findVirtual(Class.class, "cast", idType); - CAST_REFERENCE = IMPL_LOOKUP.findStatic(ValueConversions.class, "castReference", castType); - ALWAYS_NULL = IMPL_LOOKUP.findStatic(ValueConversions.class, "alwaysNull", idType); - ALWAYS_ZERO = IMPL_LOOKUP.findStatic(ValueConversions.class, "alwaysZero", alwaysZeroType); - ZERO_OBJECT = IMPL_LOOKUP.findStatic(ValueConversions.class, "zeroObject", zeroObjectType); - IGNORE = IMPL_LOOKUP.findStatic(ValueConversions.class, "ignore", ignoreType); - EMPTY = IMPL_LOOKUP.findStatic(ValueConversions.class, "empty", ignoreType.dropParameterTypes(0, 1)); - } catch (Exception ex) { + CAST_REFERENCE = IMPL_LOOKUP.findStatic(THIS_CLASS, "castReference", castType); + ALWAYS_NULL = IMPL_LOOKUP.findStatic(THIS_CLASS, "alwaysNull", idType); + ALWAYS_ZERO = IMPL_LOOKUP.findStatic(THIS_CLASS, "alwaysZero", alwaysZeroType); + ZERO_OBJECT = IMPL_LOOKUP.findStatic(THIS_CLASS, "zeroObject", zeroObjectType); + IGNORE = IMPL_LOOKUP.findStatic(THIS_CLASS, "ignore", ignoreType); + EMPTY = IMPL_LOOKUP.findStatic(THIS_CLASS, "empty", ignoreType.dropParameterTypes(0, 1)); + NEW_ARRAY = IMPL_LOOKUP.findStatic(THIS_CLASS, "newArray", MethodType.methodType(Object[].class, int.class)); + } catch (NoSuchMethodException | IllegalAccessException ex) { Error err = new InternalError("uncaught exception"); err.initCause(ex); throw err; } } - private static final EnumMap WRAPPER_CASTS - = new EnumMap(Wrapper.class); + // Varargs methods need to be in a separately initialized class, to bootstrapping problems. + static class LazyStatics { + private static final MethodHandle COPY_AS_REFERENCE_ARRAY, COPY_AS_PRIMITIVE_ARRAY, MAKE_LIST; + static { + try { + //MAKE_ARRAY = IMPL_LOOKUP.findStatic(THIS_CLASS, "makeArray", MethodType.methodType(Object[].class, Object[].class)); + COPY_AS_REFERENCE_ARRAY = IMPL_LOOKUP.findStatic(THIS_CLASS, "copyAsReferenceArray", MethodType.methodType(Object[].class, Class.class, Object[].class)); + COPY_AS_PRIMITIVE_ARRAY = IMPL_LOOKUP.findStatic(THIS_CLASS, "copyAsPrimitiveArray", MethodType.methodType(Object.class, Wrapper.class, Object[].class)); + MAKE_LIST = IMPL_LOOKUP.findStatic(THIS_CLASS, "makeList", MethodType.methodType(List.class, Object[].class)); + } catch (ReflectiveOperationException ex) { + Error err = new InternalError("uncaught exception"); + err.initCause(ex); + throw err; + } + } + } - private static final EnumMap EXACT_WRAPPER_CASTS - = new EnumMap(Wrapper.class); + private static final EnumMap[] WRAPPER_CASTS + = newWrapperCaches(2); /** Return a method that casts its sole argument (an Object) to the given type * and returns it as the given type (if exact is true), or as plain Object (if erase is true). */ - public static MethodHandle cast(Class type, boolean exact) { + public static MethodHandle cast(Class type) { + boolean exact = false; if (type.isPrimitive()) throw new IllegalArgumentException("cannot cast primitive type "+type); MethodHandle mh = null; Wrapper wrap = null; EnumMap cache = null; if (Wrapper.isWrapperType(type)) { wrap = Wrapper.forWrapperType(type); - cache = (exact ? EXACT_WRAPPER_CASTS : WRAPPER_CASTS); + cache = WRAPPER_CASTS[exact?1:0]; mh = cache.get(wrap); if (mh != null) return mh; } @@ -673,7 +739,7 @@ if (wrap != Wrapper.VOID) type = type.appendParameterTypes(wrap.primitiveType()); try { - mh = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", type); + mh = IMPL_LOOKUP.findStatic(THIS_CLASS, "identity", type); } catch (ReflectiveOperationException ex) { mh = null; } @@ -692,6 +758,210 @@ throw new IllegalArgumentException("cannot find identity for " + wrap); } + /// Float/non-float conversions. + + static float doubleToFloat(double x) { + return (float) x; + } + static double floatToDouble(float x) { + return x; + } + + // narrow double to integral type + static long doubleToLong(double x) { + return (long) x; + } + static int doubleToInt(double x) { + return (int) x; + } + static short doubleToShort(double x) { + return (short) x; + } + static char doubleToChar(double x) { + return (char) x; + } + static byte doubleToByte(double x) { + return (byte) x; + } + static boolean doubleToBoolean(double x) { + return toBoolean((byte) x); + } + + // narrow float to integral type + static long floatToLong(float x) { + return (long) x; + } + static int floatToInt(float x) { + return (int) x; + } + static short floatToShort(float x) { + return (short) x; + } + static char floatToChar(float x) { + return (char) x; + } + static byte floatToByte(float x) { + return (byte) x; + } + static boolean floatToBoolean(float x) { + return toBoolean((byte) x); + } + + // widen integral type to double + static double longToDouble(long x) { + return x; + } + static double intToDouble(int x) { + return x; + } + static double shortToDouble(short x) { + return x; + } + static double charToDouble(char x) { + return x; + } + static double byteToDouble(byte x) { + return x; + } + static double booleanToDouble(boolean x) { + return fromBoolean(x); + } + + // widen integral type to float + static float longToFloat(long x) { + return x; + } + static float intToFloat(int x) { + return x; + } + static float shortToFloat(short x) { + return x; + } + static float charToFloat(char x) { + return x; + } + static float byteToFloat(byte x) { + return x; + } + static float booleanToFloat(boolean x) { + return fromBoolean(x); + } + + static boolean toBoolean(byte x) { + // see javadoc for MethodHandles.explicitCastArguments + return ((x & 1) != 0); + } + static byte fromBoolean(boolean x) { + // see javadoc for MethodHandles.explicitCastArguments + return (x ? (byte)1 : (byte)0); + } + + private static final EnumMap[] + CONVERT_FLOAT_FUNCTIONS = newWrapperCaches(4); + + static MethodHandle convertFloatFunction(Wrapper wrap, boolean toFloat, boolean doubleSize) { + EnumMap cache = CONVERT_FLOAT_FUNCTIONS[(toFloat?1:0)+(doubleSize?2:0)]; + MethodHandle mh = cache.get(wrap); + if (mh != null) { + return mh; + } + // slow path + Wrapper fwrap = (doubleSize ? Wrapper.DOUBLE : Wrapper.FLOAT); + Class fix = wrap.primitiveType(); + Class flt = (doubleSize ? double.class : float.class); + Class src = toFloat ? fix : flt; + Class dst = toFloat ? flt : fix; + if (src == dst) return identity(wrap); + MethodType type = MethodType.methodType(dst, src); + switch (wrap) { + case VOID: + mh = toFloat ? zeroConstantFunction(fwrap) : MethodHandles.dropArguments(EMPTY, 0, flt); + break; + case OBJECT: + mh = toFloat ? unbox(flt) : box(flt); + break; + default: + try { + mh = IMPL_LOOKUP.findStatic(THIS_CLASS, src.getSimpleName()+"To"+capitalize(dst.getSimpleName()), type); + } catch (ReflectiveOperationException ex) { + mh = null; + } + break; + } + if (mh != null) { + assert(mh.type() == type) : mh; + cache.put(wrap, mh); + return mh; + } + + throw new IllegalArgumentException("cannot find float conversion constant for " + + src.getSimpleName()+" -> "+dst.getSimpleName()); + } + + public static MethodHandle convertFromFloat(Class fixType) { + Wrapper wrap = Wrapper.forPrimitiveType(fixType); + return convertFloatFunction(wrap, false, false); + } + public static MethodHandle convertFromDouble(Class fixType) { + Wrapper wrap = Wrapper.forPrimitiveType(fixType); + return convertFloatFunction(wrap, false, true); + } + public static MethodHandle convertToFloat(Class fixType) { + Wrapper wrap = Wrapper.forPrimitiveType(fixType); + return convertFloatFunction(wrap, true, false); + } + public static MethodHandle convertToDouble(Class fixType) { + Wrapper wrap = Wrapper.forPrimitiveType(fixType); + return convertFloatFunction(wrap, true, true); + } + + private static String capitalize(String x) { + return Character.toUpperCase(x.charAt(0))+x.substring(1); + } + + /// Collection of multiple arguments. + + public static Object convertArrayElements(Class arrayType, Object array) { + Class src = array.getClass().getComponentType(); + Class dst = arrayType.getComponentType(); + if (src == null || dst == null) throw new IllegalArgumentException("not array type"); + Wrapper sw = (src.isPrimitive() ? Wrapper.forPrimitiveType(src) : null); + Wrapper dw = (dst.isPrimitive() ? Wrapper.forPrimitiveType(dst) : null); + int length; + if (sw == null) { + Object[] a = (Object[]) array; + length = a.length; + if (dw == null) + return Arrays.copyOf(a, length, arrayType.asSubclass(Object[].class)); + Object res = dw.makeArray(length); + dw.copyArrayUnboxing(a, 0, res, 0, length); + return res; + } + length = java.lang.reflect.Array.getLength(array); + Object[] res; + if (dw == null) { + res = Arrays.copyOf(NO_ARGS_ARRAY, length, arrayType.asSubclass(Object[].class)); + } else { + res = new Object[length]; + } + sw.copyArrayBoxing(array, 0, res, 0, length); + if (dw == null) return res; + Object a = dw.makeArray(length); + dw.copyArrayUnboxing(res, 0, a, 0, length); + return a; + } + + private static MethodHandle findCollector(String name, int nargs, Class rtype, Class... ptypes) { + MethodType type = MethodType.genericMethodType(nargs) + .changeReturnType(rtype) + .insertParameterTypes(0, ptypes); + try { + return IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); + } catch (ReflectiveOperationException ex) { + return null; + } + } + private static final Object[] NO_ARGS_ARRAY = {}; private static Object[] makeArray(Object... args) { return args; } private static Object[] array() { return NO_ARGS_ARRAY; } @@ -723,35 +993,175 @@ Object a4, Object a5, Object a6, Object a7, Object a8, Object a9) { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); } - static MethodHandle[] makeArrays() { - ArrayList arrays = new ArrayList(); - MethodHandles.Lookup lookup = IMPL_LOOKUP; + private static MethodHandle[] makeArrays() { + ArrayList mhs = new ArrayList<>(); for (;;) { - int nargs = arrays.size(); - MethodType type = MethodType.genericMethodType(nargs).changeReturnType(Object[].class); - String name = "array"; - MethodHandle array = null; - try { - array = lookup.findStatic(ValueConversions.class, name, type); - } catch (ReflectiveOperationException ex) { - } - if (array == null) break; - arrays.add(array); + MethodHandle mh = findCollector("array", mhs.size(), Object[].class); + if (mh == null) break; + mhs.add(mh); } - assert(arrays.size() == 11); // current number of methods - return arrays.toArray(new MethodHandle[0]); + assert(mhs.size() == 11); // current number of methods + return mhs.toArray(new MethodHandle[MAX_ARITY+1]); + } + private static final MethodHandle[] ARRAYS = makeArrays(); + + // mh-fill versions of the above: + private static Object[] newArray(int len) { return new Object[len]; } + private static void fillWithArguments(Object[] a, int pos, Object... args) { + System.arraycopy(args, 0, a, pos, args.length); } - static final MethodHandle[] ARRAYS = makeArrays(); + // using Integer pos instead of int pos to avoid bootstrapping problems + private static Object[] fillArray(Object[] a, Integer pos, Object a0) + { fillWithArguments(a, pos, a0); return a; } + private static Object[] fillArray(Object[] a, Integer pos, Object a0, Object a1) + { fillWithArguments(a, pos, a0, a1); return a; } + private static Object[] fillArray(Object[] a, Integer pos, Object a0, Object a1, Object a2) + { fillWithArguments(a, pos, a0, a1, a2); return a; } + private static Object[] fillArray(Object[] a, Integer pos, Object a0, Object a1, Object a2, Object a3) + { fillWithArguments(a, pos, a0, a1, a2, a3); return a; } + private static Object[] fillArray(Object[] a, Integer pos, Object a0, Object a1, Object a2, Object a3, + Object a4) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4); return a; } + private static Object[] fillArray(Object[] a, Integer pos, Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4, a5); return a; } + private static Object[] fillArray(Object[] a, Integer pos, Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4, a5, a6); return a; } + private static Object[] fillArray(Object[] a, Integer pos, Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4, a5, a6, a7); return a; } + private static Object[] fillArray(Object[] a, Integer pos, Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7, + Object a8) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4, a5, a6, a7, a8); return a; } + private static Object[] fillArray(Object[] a, Integer pos, Object a0, Object a1, Object a2, Object a3, + Object a4, Object a5, Object a6, Object a7, + Object a8, Object a9) + { fillWithArguments(a, pos, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); return a; } + private static MethodHandle[] makeFillArrays() { + ArrayList mhs = new ArrayList<>(); + mhs.add(null); // there is no empty fill; at least a0 is required + for (;;) { + MethodHandle mh = findCollector("fillArray", mhs.size(), Object[].class, Object[].class, Integer.class); + if (mh == null) break; + mhs.add(mh); + } + assert(mhs.size() == 11); // current number of methods + return mhs.toArray(new MethodHandle[0]); + } + private static final MethodHandle[] FILL_ARRAYS = makeFillArrays(); + + private static Object[] copyAsReferenceArray(Class arrayType, Object... a) { + return Arrays.copyOf(a, a.length, arrayType); + } + private static Object copyAsPrimitiveArray(Wrapper w, Object... boxes) { + Object a = w.makeArray(boxes.length); + w.copyArrayUnboxing(boxes, 0, a, 0, boxes.length); + return a; + } /** Return a method handle that takes the indicated number of Object * arguments and returns an Object array of them, as if for varargs. */ public static MethodHandle varargsArray(int nargs) { - if (nargs < ARRAYS.length) - return ARRAYS[nargs]; - // else need to spin bytecode or do something else fancy - throw new UnsupportedOperationException("NYI: cannot form a varargs array of length "+nargs); + MethodHandle mh = ARRAYS[nargs]; + if (mh != null) return mh; + mh = findCollector("array", nargs, Object[].class); + if (mh != null) return ARRAYS[nargs] = mh; + MethodHandle producer = filler(0); // identity function produces result + return ARRAYS[nargs] = buildVarargsArray(producer, nargs); + } + + private static MethodHandle buildVarargsArray(MethodHandle producer, int nargs) { + // Build up the result mh as a sequence of fills like this: + // producer(fill(fill(fill(newArray(23),0,x1..x10),10,x11..x20),20,x21..x23)) + // The various fill(_,10*I,___*[J]) are reusable. + MethodHandle filler = filler(nargs); + MethodHandle mh = producer; + mh = MethodHandles.dropArguments(mh, 1, filler.type().parameterList()); + mh = MethodHandles.foldArguments(mh, filler); + mh = MethodHandles.foldArguments(mh, buildNewArray(nargs)); + return mh; + } + + private static MethodHandle buildNewArray(int nargs) { + return MethodHandles.insertArguments(NEW_ARRAY, 0, (int) nargs); + } + + private static final MethodHandle[] FILLERS = new MethodHandle[MAX_ARITY+1]; + // filler(N).invoke(a, arg0..arg[N-1]) fills a[0]..a[N-1] + private static MethodHandle filler(int nargs) { + MethodHandle filler = FILLERS[nargs]; + if (filler != null) return filler; + return FILLERS[nargs] = buildFiller(nargs); } + private static MethodHandle buildFiller(int nargs) { + if (nargs == 0) + return MethodHandles.identity(Object[].class); + final int CHUNK = (FILL_ARRAYS.length - 1); + int rightLen = nargs % CHUNK; + int leftLen = nargs - rightLen; + if (rightLen == 0) { + leftLen = nargs - (rightLen = CHUNK); + if (FILLERS[leftLen] == null) { + // build some precursors from left to right + for (int j = 0; j < leftLen; j += CHUNK) filler(j); + } + } + MethodHandle leftFill = filler(leftLen); // recursive fill + MethodHandle rightFill = FILL_ARRAYS[rightLen]; + rightFill = MethodHandles.insertArguments(rightFill, 1, (int) leftLen); // [leftLen..nargs-1] + + // Combine the two fills: right(left(newArray(nargs), x1..x20), x21..x23) + MethodHandle mh = filler(0); // identity function produces result + mh = MethodHandles.dropArguments(mh, 1, rightFill.type().parameterList()); + mh = MethodHandles.foldArguments(mh, rightFill); + if (leftLen > 0) { + mh = MethodHandles.dropArguments(mh, 1, leftFill.type().parameterList()); + mh = MethodHandles.foldArguments(mh, leftFill); + } + return mh; + } + + // Type-polymorphic version of varargs maker. + private static final ClassValue TYPED_COLLECTORS + = new ClassValue() { + protected MethodHandle[] computeValue(Class type) { + return new MethodHandle[256]; + } + }; + + /** Return a method handle that takes the indicated number of + * typed arguments and returns an array of them. + * The type argument is the array type. + */ + public static MethodHandle varargsArray(Class arrayType, int nargs) { + Class elemType = arrayType.getComponentType(); + if (elemType == null) throw new IllegalArgumentException("not an array: "+arrayType); + // FIXME: Need more special casing and caching here. + if (elemType == Object.class) + return varargsArray(nargs); + // other cases: primitive arrays, subtypes of Object[] + MethodHandle cache[] = TYPED_COLLECTORS.get(elemType); + MethodHandle mh = nargs < cache.length ? cache[nargs] : null; + if (mh != null) return mh; + MethodHandle producer = buildArrayProducer(arrayType); + mh = buildVarargsArray(producer, nargs); + mh = mh.asType(MethodType.methodType(arrayType, Collections.>nCopies(nargs, elemType))); + cache[nargs] = mh; + return mh; + } + + private static MethodHandle buildArrayProducer(Class arrayType) { + Class elemType = arrayType.getComponentType(); + if (elemType.isPrimitive()) + return LazyStatics.COPY_AS_PRIMITIVE_ARRAY.bindTo(Wrapper.forPrimitiveType(elemType)); + else + return LazyStatics.COPY_AS_REFERENCE_ARRAY.bindTo(arrayType); + } + + // List version of varargs maker. private static final List NO_ARGS_LIST = Arrays.asList(NO_ARGS_ARRAY); private static List makeList(Object... args) { return Arrays.asList(args); } @@ -784,34 +1194,29 @@ Object a4, Object a5, Object a6, Object a7, Object a8, Object a9) { return makeList(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); } - static MethodHandle[] makeLists() { - ArrayList arrays = new ArrayList(); - MethodHandles.Lookup lookup = IMPL_LOOKUP; + private static MethodHandle[] makeLists() { + ArrayList mhs = new ArrayList<>(); for (;;) { - int nargs = arrays.size(); - MethodType type = MethodType.genericMethodType(nargs).changeReturnType(List.class); - String name = "list"; - MethodHandle array = null; - try { - array = lookup.findStatic(ValueConversions.class, name, type); - } catch (ReflectiveOperationException ex) { - } - if (array == null) break; - arrays.add(array); + MethodHandle mh = findCollector("list", mhs.size(), List.class); + if (mh == null) break; + mhs.add(mh); } - assert(arrays.size() == 11); // current number of methods - return arrays.toArray(new MethodHandle[0]); + assert(mhs.size() == 11); // current number of methods + return mhs.toArray(new MethodHandle[MAX_ARITY+1]); } - static final MethodHandle[] LISTS = makeLists(); + private static final MethodHandle[] LISTS = makeLists(); /** Return a method handle that takes the indicated number of Object - * arguments and returns List. + * arguments and returns a List. */ public static MethodHandle varargsList(int nargs) { - if (nargs < LISTS.length) - return LISTS[nargs]; - // else need to spin bytecode or do something else fancy - throw new UnsupportedOperationException("NYI"); + MethodHandle mh = LISTS[nargs]; + if (mh != null) return mh; + mh = findCollector("list", nargs, List.class); + if (mh != null) return LISTS[nargs] = mh; + return LISTS[nargs] = buildVarargsList(nargs); + } + private static MethodHandle buildVarargsList(int nargs) { + return MethodHandles.filterReturnValue(varargsArray(nargs), LazyStatics.MAKE_LIST); } } - diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/sun/invoke/util/VerifyType.java --- a/jdk/src/share/classes/sun/invoke/util/VerifyType.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/sun/invoke/util/VerifyType.java Tue May 17 14:29:59 2011 -0700 @@ -54,9 +54,15 @@ if (dst == void.class) return true; // drop any return value if (isNullType(src)) return !dst.isPrimitive(); if (!src.isPrimitive()) return dst.isAssignableFrom(src); + if (!dst.isPrimitive()) return false; // Verifier allows an int to carry byte, short, char, or even boolean: - if (dst == int.class) return Wrapper.forPrimitiveType(src).isSubwordOrInt(); - return false; + Wrapper sw = Wrapper.forPrimitiveType(src); + if (dst == int.class) return sw.isSubwordOrInt(); + Wrapper dw = Wrapper.forPrimitiveType(dst); + if (!sw.isSubwordOrInt()) return false; + if (!dw.isSubwordOrInt()) return false; + if (!dw.isSigned() && sw.isSigned()) return false; + return dw.bitWidth() > sw.bitWidth(); } /** @@ -155,6 +161,7 @@ return -1; // truncation may be required if (!dw.isSigned() && sw.isSigned()) return -1; // sign elimination may be required + return 1; } if (src == float.class || dst == float.class) { if (src == double.class || dst == double.class) diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/src/share/classes/sun/invoke/util/Wrapper.java --- a/jdk/src/share/classes/sun/invoke/util/Wrapper.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/src/share/classes/sun/invoke/util/Wrapper.java Tue May 17 14:29:59 2011 -0700 @@ -26,37 +26,47 @@ package sun.invoke.util; public enum Wrapper { - BOOLEAN(Boolean.class, boolean.class, 'Z', (Boolean)false, Format.unsigned(1)), + BOOLEAN(Boolean.class, boolean.class, 'Z', (Boolean)false, new boolean[0], Format.unsigned(1)), // These must be in the order defined for widening primitive conversions in JLS 5.1.2 - BYTE(Byte.class, byte.class, 'B', (Byte)(byte)0, Format.signed(8)), - SHORT(Short.class, short.class, 'S', (Short)(short)0, Format.signed(16)), - CHAR(Character.class, char.class, 'C', (Character)(char)0, Format.unsigned(16)), - INT(Integer.class, int.class, 'I', (Integer)(int)0, Format.signed(32)), - LONG(Long.class, long.class, 'J', (Long)(long)0, Format.signed(64)), - FLOAT(Float.class, float.class, 'F', (Float)(float)0, Format.floating(32)), - DOUBLE(Double.class, double.class, 'D', (Double)(double)0, Format.floating(64)), - //NULL(Null.class, null.class, 'N', null, Format.other(1)), - OBJECT(Object.class, Object.class, 'L', null, Format.other(1)), + BYTE(Byte.class, byte.class, 'B', (Byte)(byte)0, new byte[0], Format.signed(8)), + SHORT(Short.class, short.class, 'S', (Short)(short)0, new short[0], Format.signed(16)), + CHAR(Character.class, char.class, 'C', (Character)(char)0, new char[0], Format.unsigned(16)), + INT(Integer.class, int.class, 'I', (Integer)(int)0, new int[0], Format.signed(32)), + LONG(Long.class, long.class, 'J', (Long)(long)0, new long[0], Format.signed(64)), + FLOAT(Float.class, float.class, 'F', (Float)(float)0, new float[0], Format.floating(32)), + DOUBLE(Double.class, double.class, 'D', (Double)(double)0, new double[0], Format.floating(64)), + //NULL(Null.class, null.class, 'N', null, null, Format.other(1)), + OBJECT(Object.class, Object.class, 'L', null, new Object[0], Format.other(1)), // VOID must be the last type, since it is "assignable" from any other type: - VOID(Void.class, void.class, 'V', null, Format.other(0)), + VOID(Void.class, void.class, 'V', null, null, Format.other(0)), ; private final Class wrapperType; private final Class primitiveType; private final char basicTypeChar; private final Object zero; + private final Object emptyArray; private final int format; private final String simpleName; - private Wrapper(Class wtype, Class ptype, char tchar, Object zero, int format) { + private Wrapper(Class wtype, Class ptype, char tchar, Object zero, Object emptyArray, int format) { this.wrapperType = wtype; this.primitiveType = ptype; this.basicTypeChar = tchar; this.zero = zero; + this.emptyArray = emptyArray; this.format = format; this.simpleName = wtype.getSimpleName(); } + /** For debugging, give the details of this wrapper. */ + public String detailString() { + return simpleName+ + java.util.Arrays.asList(wrapperType, primitiveType, + basicTypeChar, zero, + "0x"+Integer.toHexString(format)); + } + private static abstract class Format { static final int SLOT_SHIFT = 0, SIZE_SHIFT = 2, KIND_SHIFT = 12; static final int @@ -114,16 +124,18 @@ public boolean isUnsigned() { return format >= Format.BOOLEAN && format < Format.FLOAT; } /** Is the wrapped type either float or double? */ public boolean isFloating() { return format >= Format.FLOAT; } + /** Is the wrapped type either void or a reference? */ + public boolean isOther() { return (format & ~Format.SLOT_MASK) == 0; } - /** Does the JVM verifier allow a variable of this wrapper's + /** Does the JLS 5.1.2 allow a variable of this wrapper's * primitive type to be assigned from a value of the given wrapper's primitive type? * Cases: *
      *
    • unboxing followed by widening primitive conversion - *
    • any type converted to {@code void} + *
    • any type converted to {@code void} (i.e., dropping a method call's value) *
    • boxing conversion followed by widening reference conversion to {@code Object} - *
    • conversion of {@code boolean} to any type *
    + * These are the cases allowed by MethodHandle.asType and convertArguments. */ public boolean isConvertibleFrom(Wrapper source) { if (this == source) return true; @@ -131,13 +143,75 @@ // At best, this is a narrowing conversion. return false; } - if ((this.format ^ source.format) == (Format.SHORT ^ Format.CHAR)) { - assert (this == SHORT && source == CHAR) || (this == CHAR && source == SHORT); + // All conversions are allowed in the enum order between floats and signed ints. + // First detect non-signed non-float types (boolean, char, Object, void). + boolean floatOrSigned = (((this.format & source.format) & Format.SIGNED) != 0); + if (!floatOrSigned) { + if (this.isOther()) return true; + // can convert char to int or wider, but nothing else + if (source.format == Format.CHAR) return true; + // no other conversions are classified as widening return false; } + // All signed and float conversions in the enum order are widening. + assert(this.isFloating() || this.isSigned()); + assert(source.isFloating() || source.isSigned()); return true; } + static { assert(checkConvertibleFrom()); } + private static boolean checkConvertibleFrom() { + // Check the matrix for correct classification of widening conversions. + for (Wrapper w : values()) { + assert(w.isConvertibleFrom(w)); + assert(VOID.isConvertibleFrom(w)); + if (w != VOID) { + assert(OBJECT.isConvertibleFrom(w)); + assert(!w.isConvertibleFrom(VOID)); + } + // check relations with unsigned integral types: + if (w != CHAR) { + assert(!CHAR.isConvertibleFrom(w)); + if (!w.isConvertibleFrom(INT)) + assert(!w.isConvertibleFrom(CHAR)); + } + if (w != BOOLEAN) { + assert(!BOOLEAN.isConvertibleFrom(w)); + if (w != VOID && w != OBJECT) + assert(!w.isConvertibleFrom(BOOLEAN)); + } + // check relations with signed integral types: + if (w.isSigned()) { + for (Wrapper x : values()) { + if (w == x) continue; + if (x.isFloating()) + assert(!w.isConvertibleFrom(x)); + else if (x.isSigned()) { + if (w.compareTo(x) < 0) + assert(!w.isConvertibleFrom(x)); + else + assert(w.isConvertibleFrom(x)); + } + } + } + // check relations with floating types: + if (w.isFloating()) { + for (Wrapper x : values()) { + if (w == x) continue; + if (x.isSigned()) + assert(w.isConvertibleFrom(x)); + else if (x.isFloating()) { + if (w.compareTo(x) < 0) + assert(!w.isConvertibleFrom(x)); + else + assert(w.isConvertibleFrom(x)); + } + } + } + } + return true; // i.e., assert(true) + } + /** Produce a zero value for the given wrapper type. * This will be a numeric zero for a number or character, * false for a boolean, and null for a reference or void. @@ -549,7 +623,7 @@ } private static boolean boolValue(long bits) { - //bits &= 1; // simple 31-bit zero extension + bits &= 1; // simple 31-bit zero extension return (bits != 0); } @@ -559,4 +633,31 @@ private static RuntimeException newIllegalArgumentException(String message) { return new IllegalArgumentException(message); } + + // primitive array support + public Object makeArray(int len) { + return java.lang.reflect.Array.newInstance(primitiveType, len); + } + public Class arrayType() { + return emptyArray.getClass(); + } + public void copyArrayUnboxing(Object[] values, int vpos, Object a, int apos, int length) { + if (a.getClass() != arrayType()) + arrayType().cast(a); // throw NPE or CCE if bad type + for (int i = 0; i < length; i++) { + Object value = values[i+vpos]; + value = convert(value, primitiveType); + java.lang.reflect.Array.set(a, i+apos, value); + } + } + public void copyArrayBoxing(Object a, int apos, Object[] values, int vpos, int length) { + if (a.getClass() != arrayType()) + arrayType().cast(a); // throw NPE or CCE if bad type + for (int i = 0; i < length; i++) { + Object value = java.lang.reflect.Array.get(a, i+apos); + //Already done: value = convert(value, primitiveType); + assert(value.getClass() == wrapperType); + values[i+vpos] = value; + } + } } diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/test/java/lang/invoke/6998541/Test6998541.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/lang/invoke/6998541/Test6998541.java Tue May 17 14:29:59 2011 -0700 @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/** + * @test + * @bug 6998541 + * @summary JSR 292 implement missing return-type conversion for OP_RETYPE_RAW + * + * @run main/othervm -Xbatch + * -XX:+UnlockDiagnosticVMOptions -XX:ScavengeRootsInCode=2 + * -DTest6998541.N=100000 -DTest6998541.KIND=cast Test6998541 + * @run main/othervm -Xbatch + * -XX:+UnlockDiagnosticVMOptions -XX:ScavengeRootsInCode=2 + * -DTest6998541.N=100000 -DTest6998541.KIND=normal Test6998541 + */ + +import java.util.*; + +import java.lang.invoke.*; +import static java.lang.invoke.MethodHandles.*; + +public class Test6998541 { + private static final Class CLASS = Test6998541.class; + private static final String NAME = "identity"; + private static final int N = Math.max(2, Integer.getInteger(CLASS.getSimpleName()+".N", 10000)); + private static final String KIND = System.getProperty(CLASS.getSimpleName()+".KIND", "cast"); + private static final int BITS = 0x00000201; + + private static final boolean DO_CASTS = !KIND.equals("normal"); + + public static void main(String[] args) throws Throwable { + System.out.println("KIND="+KIND+" DO_CASTS="+DO_CASTS+" N="+N); + doboolean(); + dobyte(); + dochar(); + doshort(); + doint(); + dolong(); + dofloat(); + dodouble(); + dovoid(); + } + + private static void doboolean() throws Throwable { + for (int i = 0; i < N; i++) { + boolean2prim(false); + boolean2prim(true); + } + boolean2prim_invalid(true); + } + private static void dobyte() throws Throwable { + byte x = Byte.MIN_VALUE; + for (int i = 0; i < N; i++, x++) + byte2prim(x); + byte2prim_invalid(x); + } + private static void dochar() throws Throwable { + char x = Character.MIN_VALUE; + for (int i = 0; i < N; i++, x++) + char2prim(x); + char2prim_invalid(x); + } + private static void doshort() throws Throwable { + short x = Short.MIN_VALUE; + for (int i = 0; i < N; i++, x++) + short2prim(x); + short2prim_invalid(x); + } + private static void doint() throws Throwable { + int x = Integer.MIN_VALUE; + int D = Integer.MAX_VALUE / (N / 2) | BITS; + for (int i = 0; i < N; i++, x += D) { + int2prim(x); + } + int2prim_invalid(x); + } + private static void dolong() throws Throwable { + long x = Long.MIN_VALUE; + long D = Long.MAX_VALUE / ((long) (N / 2)) | BITS; + for (int i = 0; i < N; i++, x += D) + long2prim(x); + long2prim_invalid(x); + } + private static void dofloat() throws Throwable { + float x = Float.MIN_VALUE; + float D = Float.MAX_VALUE / ((float) (N / 2)); + for (int i = 0; i < N; i++, x += D) + float2prim(x); + float2prim_invalid(x); + } + private static void dodouble() throws Throwable { + double x = Double.MIN_VALUE; + double D = Double.MAX_VALUE / ((double) (N / 2)); + for (int i = 0; i < N; i++, x += D) + double2prim(x); + double2prim_invalid(x); + } + private static void dovoid() throws Throwable { + for (int i = 0; i < N; i++) { + void2prim(i); + } + void2prim_invalid(0); + // do the other direction here also: + for (int i = 0; i < N; i++) { + prim2void(i); + } + prim2void_invalid(0); + } + + private static void assertEquals(Object o, Object o2) { + if (!o.equals(o2)) + throw new AssertionError("expected: " + o + ", found: " + o2); + } + private static void fail() { + throw new AssertionError(); + } + + private final static MethodHandles.Lookup lookup = MethodHandles.lookup(); + + private static MethodHandle mh(Class ret, Class... args) { + try { + MethodType mt = MethodType.methodType(ret, args); + Class lookupRet = (args.length == 0 ? void.class : args[0]); + MethodHandle mh = lookup.findStatic(CLASS, NAME, mt.changeReturnType(lookupRet)); + if (DO_CASTS) + return MethodHandles.explicitCastArguments(mh, mt); + if (canDoAsType(mh.type(), mt)) + return mh.asType(mt); + try { + mh.asType(mt); + throw new AssertionError("asType should not succeed: "+mh+" => "+mt); + } catch (WrongMethodTypeException ex) { + // this was a required WMTE + return mh.asType(mt.generic()).asType(mt); + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + private static final Class[] NUMERIC_TYPE_WIDENING_ORDER = { + byte.class, short.class, int.class, long.class, float.class, double.class + }; + private static boolean canDoAsType(Class src, Class dst) { + if (src == dst) return true; + if (dst == void.class) return true; + if (!src.isPrimitive() || !dst.isPrimitive()) return true; + // primitive conversion works for asType only when it's widening + if (src == boolean.class || dst == boolean.class) return false; + if (dst == char.class) return false; + if (src == char.class) src = int.class; // can widen char to int + for (Class ntype : NUMERIC_TYPE_WIDENING_ORDER) { + if (src == ntype) return true; + if (dst == ntype) return false; + } + throw new AssertionError("should not reach here: "+src+", "+dst); + } + private static boolean canDoAsType(MethodType mt0, MethodType mt1) { + Class rt0 = mt0.returnType(); + Class rt1 = mt1.returnType(); + if (!canDoAsType(rt0, rt1)) return false; + int argc = mt0.parameterCount(); + if (argc != mt1.parameterCount()) return false; + for (int i = 0; i < argc; i++) { + if (!canDoAsType(mt1.parameterType(i), mt0.parameterType(i))) + return false; + } + return true; + } + + private static MethodHandle mh_z(Class ret) { return mh(ret, boolean.class); } + + private final static MethodHandle mh_zz = mh_z(boolean.class); + private final static MethodHandle mh_bz = mh_z(byte.class ); + private final static MethodHandle mh_cz = mh_z(char.class ); + private final static MethodHandle mh_sz = mh_z(short.class ); + private final static MethodHandle mh_iz = mh_z(int.class ); + private final static MethodHandle mh_jz = mh_z(long.class ); + private final static MethodHandle mh_fz = mh_z(float.class ); + private final static MethodHandle mh_dz = mh_z(double.class ); + + private static void boolean2prim(boolean x) throws Throwable { + int i = x ? 1 : 0; + assertEquals( x, (boolean) mh_zz.invokeExact(x)); // boolean -> boolean + if (!DO_CASTS) return; + assertEquals((byte) i, (byte) mh_bz.invokeExact(x)); // boolean -> byte + assertEquals((char) i, (char) mh_cz.invokeExact(x)); // boolean -> char + assertEquals((short) i, (short) mh_sz.invokeExact(x)); // boolean -> short + assertEquals((int) i, (int) mh_iz.invokeExact(x)); // boolean -> int + assertEquals((long) i, (long) mh_jz.invokeExact(x)); // boolean -> long + assertEquals((float) i, (float) mh_fz.invokeExact(x)); // boolean -> float + assertEquals((double) i, (double) mh_dz.invokeExact(x)); // boolean -> double + } + private static void boolean2prim_invalid(boolean x) throws Throwable { + if (DO_CASTS) return; + try { byte y = (byte) mh_bz.invokeExact(x); fail(); } catch (ClassCastException _) {} // boolean -> byte + try { char y = (char) mh_cz.invokeExact(x); fail(); } catch (ClassCastException _) {} // boolean -> char + try { short y = (short) mh_sz.invokeExact(x); fail(); } catch (ClassCastException _) {} // boolean -> short + try { int y = (int) mh_iz.invokeExact(x); fail(); } catch (ClassCastException _) {} // boolean -> int + try { long y = (long) mh_jz.invokeExact(x); fail(); } catch (ClassCastException _) {} // boolean -> long + try { float y = (float) mh_fz.invokeExact(x); fail(); } catch (ClassCastException _) {} // boolean -> float + try { double y = (double) mh_dz.invokeExact(x); fail(); } catch (ClassCastException _) {} // boolean -> double + } + + private static MethodHandle mh_b(Class ret) { return mh(ret, byte.class); } + + private final static MethodHandle mh_zb = mh_b(boolean.class); + private final static MethodHandle mh_bb = mh_b(byte.class ); + private final static MethodHandle mh_cb = mh_b(char.class ); + private final static MethodHandle mh_sb = mh_b(short.class ); + private final static MethodHandle mh_ib = mh_b(int.class ); + private final static MethodHandle mh_jb = mh_b(long.class ); + private final static MethodHandle mh_fb = mh_b(float.class ); + private final static MethodHandle mh_db = mh_b(double.class ); + + private static void byte2prim(byte x) throws Throwable { + assertEquals((byte) x, (byte) mh_bb.invokeExact(x)); // byte -> byte + assertEquals((short) x, (short) mh_sb.invokeExact(x)); // byte -> short + assertEquals((int) x, (int) mh_ib.invokeExact(x)); // byte -> int + assertEquals((long) x, (long) mh_jb.invokeExact(x)); // byte -> long + assertEquals((float) x, (float) mh_fb.invokeExact(x)); // byte -> float + assertEquals((double) x, (double) mh_db.invokeExact(x)); // byte -> double + if (!DO_CASTS) return; + boolean z = ((x & 1) != 0); + assertEquals((char) x, (char) mh_cb.invokeExact(x)); // byte -> char + assertEquals((boolean) z, (boolean) mh_zb.invokeExact(x)); // byte -> boolean + } + private static void byte2prim_invalid(byte x) throws Throwable { + if (DO_CASTS) return; + try { char y = (char) mh_cb.invokeExact(x); fail(); } catch (ClassCastException _) {} // byte -> char + try { boolean y = (boolean) mh_zb.invokeExact(x); fail(); } catch (ClassCastException _) {} // byte -> boolean + } + + private static MethodHandle mh_c(Class ret) { return mh(ret, char.class); } + + private final static MethodHandle mh_zc = mh_c(boolean.class); + private final static MethodHandle mh_bc = mh_c(byte.class ); + private final static MethodHandle mh_cc = mh_c(char.class ); + private final static MethodHandle mh_sc = mh_c(short.class ); + private final static MethodHandle mh_ic = mh_c(int.class ); + private final static MethodHandle mh_jc = mh_c(long.class ); + private final static MethodHandle mh_fc = mh_c(float.class ); + private final static MethodHandle mh_dc = mh_c(double.class ); + + private static void char2prim(char x) throws Throwable { + assertEquals((char) x, (char) mh_cc.invokeExact(x)); // char -> char + assertEquals((int) x, (int) mh_ic.invokeExact(x)); // char -> int + assertEquals((long) x, (long) mh_jc.invokeExact(x)); // char -> long + assertEquals((float) x, (float) mh_fc.invokeExact(x)); // char -> float + assertEquals((double) x, (double) mh_dc.invokeExact(x)); // char -> double + if (!DO_CASTS) return; + boolean z = ((x & 1) != 0); + assertEquals((boolean) z, (boolean) mh_zc.invokeExact(x)); // char -> boolean + assertEquals((byte) x, (byte) mh_bc.invokeExact(x)); // char -> byte + assertEquals((short) x, (short) mh_sc.invokeExact(x)); // char -> short + } + private static void char2prim_invalid(char x) throws Throwable { + if (DO_CASTS) return; + try { boolean y = (boolean) mh_zc.invokeExact(x); fail(); } catch (ClassCastException _) {} // char -> boolean + try { byte y = (byte) mh_bc.invokeExact(x); fail(); } catch (ClassCastException _) {} // char -> byte + try { short y = (short) mh_sc.invokeExact(x); fail(); } catch (ClassCastException _) {} // char -> short + } + + private static MethodHandle mh_s(Class ret) { return mh(ret, short.class); } + + private final static MethodHandle mh_zs = mh_s(boolean.class); + private final static MethodHandle mh_bs = mh_s(byte.class ); + private final static MethodHandle mh_cs = mh_s(char.class ); + private final static MethodHandle mh_ss = mh_s(short.class ); + private final static MethodHandle mh_is = mh_s(int.class ); + private final static MethodHandle mh_js = mh_s(long.class ); + private final static MethodHandle mh_fs = mh_s(float.class ); + private final static MethodHandle mh_ds = mh_s(double.class ); + + private static void short2prim(short x) throws Throwable { + assertEquals((short) x, (short) mh_ss.invokeExact(x)); // short -> short + assertEquals((int) x, (int) mh_is.invokeExact(x)); // short -> int + assertEquals((long) x, (long) mh_js.invokeExact(x)); // short -> long + assertEquals((float) x, (float) mh_fs.invokeExact(x)); // short -> float + assertEquals((double) x, (double) mh_ds.invokeExact(x)); // short -> double + if (!DO_CASTS) return; + boolean z = ((x & 1) != 0); + assertEquals((boolean) z, (boolean) mh_zs.invokeExact(x)); // short -> boolean + assertEquals((byte) x, (byte) mh_bs.invokeExact(x)); // short -> byte + assertEquals((char) x, (char) mh_cs.invokeExact(x)); // short -> char + } + private static void short2prim_invalid(short x) throws Throwable { + if (DO_CASTS) return; + try { boolean y = (boolean) mh_zs.invokeExact(x); fail(); } catch (ClassCastException _) {} // short -> boolean + try { byte y = (byte) mh_bs.invokeExact(x); fail(); } catch (ClassCastException _) {} // short -> byte + try { char y = (char) mh_cs.invokeExact(x); fail(); } catch (ClassCastException _) {} // short -> char + } + + private static MethodHandle mh_i(Class ret) { return mh(ret, int.class); } + + private final static MethodHandle mh_zi = mh_i(boolean.class); + private final static MethodHandle mh_bi = mh_i(byte.class ); + private final static MethodHandle mh_ci = mh_i(char.class ); + private final static MethodHandle mh_si = mh_i(short.class ); + private final static MethodHandle mh_ii = mh_i(int.class ); + private final static MethodHandle mh_ji = mh_i(long.class ); + private final static MethodHandle mh_fi = mh_i(float.class ); + private final static MethodHandle mh_di = mh_i(double.class ); + + private static void int2prim(int x) throws Throwable { + assertEquals((int) x, (int) mh_ii.invokeExact(x)); // int -> int + assertEquals((long) x, (long) mh_ji.invokeExact(x)); // int -> long + assertEquals((float) x, (float) mh_fi.invokeExact(x)); // int -> float + assertEquals((double) x, (double) mh_di.invokeExact(x)); // int -> double + if (!DO_CASTS) return; + boolean z = ((x & 1) != 0); + assertEquals((boolean) z, (boolean) mh_zi.invokeExact(x)); // int -> boolean + assertEquals((byte) x, (byte) mh_bi.invokeExact(x)); // int -> byte + assertEquals((char) x, (char) mh_ci.invokeExact(x)); // int -> char + assertEquals((short) x, (short) mh_si.invokeExact(x)); // int -> short + } + private static void int2prim_invalid(int x) throws Throwable { + if (DO_CASTS) return; + try { boolean y = (boolean) mh_zi.invokeExact(x); fail(); } catch (ClassCastException _) {} // int -> boolean + try { byte y = (byte) mh_bi.invokeExact(x); fail(); } catch (ClassCastException _) {} // int -> byte + try { char y = (char) mh_ci.invokeExact(x); fail(); } catch (ClassCastException _) {} // int -> char + try { short y = (short) mh_si.invokeExact(x); fail(); } catch (ClassCastException _) {} // int -> short + } + + private static MethodHandle mh_j(Class ret) { return mh(ret, long.class); } + + private final static MethodHandle mh_zj = mh_j(boolean.class); + private final static MethodHandle mh_bj = mh_j(byte.class ); + private final static MethodHandle mh_cj = mh_j(char.class ); + private final static MethodHandle mh_sj = mh_j(short.class ); + private final static MethodHandle mh_ij = mh_j(int.class ); + private final static MethodHandle mh_jj = mh_j(long.class ); + private final static MethodHandle mh_fj = mh_j(float.class ); + private final static MethodHandle mh_dj = mh_j(double.class ); + + private static void long2prim(long x) throws Throwable { + assertEquals((long) x, (long) mh_jj.invokeExact(x)); // long -> long + assertEquals((float) x, (float) mh_fj.invokeExact(x)); // long -> float + assertEquals((double) x, (double) mh_dj.invokeExact(x)); // long -> double + if (!DO_CASTS) return; + boolean z = ((x & 1) != 0); + assertEquals((boolean)z, (boolean) mh_zj.invokeExact(x)); // long -> boolean + assertEquals((byte) x, (byte) mh_bj.invokeExact(x)); // long -> byte + assertEquals((char) x, (char) mh_cj.invokeExact(x)); // long -> char + assertEquals((short) x, (short) mh_sj.invokeExact(x)); // long -> short + assertEquals((int) x, (int) mh_ij.invokeExact(x)); // long -> int + } + private static void long2prim_invalid(long x) throws Throwable { + if (DO_CASTS) return; + try { boolean y = (boolean) mh_zj.invokeExact(x); fail(); } catch (ClassCastException _) {} // long -> boolean + try { byte y = (byte) mh_bj.invokeExact(x); fail(); } catch (ClassCastException _) {} // long -> byte + try { char y = (char) mh_cj.invokeExact(x); fail(); } catch (ClassCastException _) {} // long -> char + try { short y = (short) mh_sj.invokeExact(x); fail(); } catch (ClassCastException _) {} // long -> short + try { int y = (int) mh_ij.invokeExact(x); fail(); } catch (ClassCastException _) {} // long -> int + } + + private static MethodHandle mh_f(Class ret) { return mh(ret, float.class); } + + private final static MethodHandle mh_zf = mh_f(boolean.class); + private final static MethodHandle mh_bf = mh_f(byte.class ); + private final static MethodHandle mh_cf = mh_f(char.class ); + private final static MethodHandle mh_sf = mh_f(short.class ); + private final static MethodHandle mh_if = mh_f(int.class ); + private final static MethodHandle mh_jf = mh_f(long.class ); + private final static MethodHandle mh_ff = mh_f(float.class ); + private final static MethodHandle mh_df = mh_f(double.class ); + + private static void float2prim(float x) throws Throwable { + assertEquals((float) x, (float) mh_ff.invokeExact(x)); // float -> float + assertEquals((double) x, (double) mh_df.invokeExact(x)); // float -> double + if (!DO_CASTS) return; + boolean z = (((byte) x & 1) != 0); + assertEquals((boolean) z, (boolean) mh_zf.invokeExact(x)); // float -> boolean + assertEquals((byte) x, (byte) mh_bf.invokeExact(x)); // float -> byte + assertEquals((char) x, (char) mh_cf.invokeExact(x)); // float -> char + assertEquals((short) x, (short) mh_sf.invokeExact(x)); // float -> short + assertEquals((int) x, (int) mh_if.invokeExact(x)); // float -> int + assertEquals((long) x, (long) mh_jf.invokeExact(x)); // float -> long + } + private static void float2prim_invalid(float x) throws Throwable { + if (DO_CASTS) return; + try { boolean y = (boolean) mh_zf.invokeExact(x); fail(); } catch (ClassCastException _) {} // float -> boolean + try { byte y = (byte) mh_bf.invokeExact(x); fail(); } catch (ClassCastException _) {} // float -> byte + try { char y = (char) mh_cf.invokeExact(x); fail(); } catch (ClassCastException _) {} // float -> char + try { short y = (short) mh_sf.invokeExact(x); fail(); } catch (ClassCastException _) {} // float -> short + try { int y = (int) mh_if.invokeExact(x); fail(); } catch (ClassCastException _) {} // float -> int + try { long y = (long) mh_jf.invokeExact(x); fail(); } catch (ClassCastException _) {} // float -> long + } + + private static MethodHandle mh_d(Class ret) { return mh(ret, double.class); } + + private final static MethodHandle mh_zd = mh_d(boolean.class); + private final static MethodHandle mh_bd = mh_d(byte.class ); + private final static MethodHandle mh_cd = mh_d(char.class ); + private final static MethodHandle mh_sd = mh_d(short.class ); + private final static MethodHandle mh_id = mh_d(int.class ); + private final static MethodHandle mh_jd = mh_d(long.class ); + private final static MethodHandle mh_fd = mh_d(float.class ); + private final static MethodHandle mh_dd = mh_d(double.class ); + + private static void double2prim(double x) throws Throwable { + assertEquals((double) x, (double) mh_dd.invokeExact(x)); // double -> double + if (!DO_CASTS) return; + boolean z = (((byte) x & 1) != 0); + assertEquals((boolean) z, (boolean) mh_zd.invokeExact(x)); // double -> boolean + assertEquals((byte) x, (byte) mh_bd.invokeExact(x)); // double -> byte + assertEquals((char) x, (char) mh_cd.invokeExact(x)); // double -> char + assertEquals((short) x, (short) mh_sd.invokeExact(x)); // double -> short + assertEquals((int) x, (int) mh_id.invokeExact(x)); // double -> int + assertEquals((long) x, (long) mh_jd.invokeExact(x)); // double -> long + assertEquals((float) x, (float) mh_fd.invokeExact(x)); // double -> float + } + private static void double2prim_invalid(double x) throws Throwable { + if (DO_CASTS) return; + try { boolean y = (boolean) mh_zd.invokeExact(x); fail(); } catch (ClassCastException _) {} // double -> boolean + try { byte y = (byte) mh_bd.invokeExact(x); fail(); } catch (ClassCastException _) {} // double -> byte + try { char y = (char) mh_cd.invokeExact(x); fail(); } catch (ClassCastException _) {} // double -> char + try { short y = (short) mh_sd.invokeExact(x); fail(); } catch (ClassCastException _) {} // double -> short + try { int y = (int) mh_id.invokeExact(x); fail(); } catch (ClassCastException _) {} // double -> int + try { long y = (long) mh_jd.invokeExact(x); fail(); } catch (ClassCastException _) {} // double -> long + try { float y = (float) mh_fd.invokeExact(x); fail(); } catch (ClassCastException _) {} // double -> float + } + + private final static MethodHandle mh_zv = mh(boolean.class); + private final static MethodHandle mh_bv = mh(byte.class ); + private final static MethodHandle mh_cv = mh(char.class ); + private final static MethodHandle mh_sv = mh(short.class ); + private final static MethodHandle mh_iv = mh(int.class ); + private final static MethodHandle mh_jv = mh(long.class ); + private final static MethodHandle mh_fv = mh(float.class ); + private final static MethodHandle mh_dv = mh(double.class ); + + private static void void2prim(int i) throws Throwable { + if (!DO_CASTS) return; + assertEquals( false, (boolean) mh_zv.invokeExact()); // void -> boolean + assertEquals((byte) 0, (byte) mh_bv.invokeExact()); // void -> byte + assertEquals((char) 0, (char) mh_cv.invokeExact()); // void -> char + assertEquals((short) 0, (short) mh_sv.invokeExact()); // void -> short + assertEquals( 0, (int) mh_iv.invokeExact()); // void -> int + assertEquals( 0L, (long) mh_jv.invokeExact()); // void -> long + assertEquals( 0.0f, (float) mh_fv.invokeExact()); // void -> float + assertEquals( 0.0d, (double) mh_dv.invokeExact()); // void -> double + } + + private static void void2prim_invalid(double x) throws Throwable { + if (DO_CASTS) return; + try { assertEquals( false, (boolean) mh_zv.invokeExact()); fail(); } catch (NullPointerException _) {} // void -> boolean + try { assertEquals((byte) 0, (byte) mh_bv.invokeExact()); fail(); } catch (NullPointerException _) {} // void -> byte + try { assertEquals((char) 0, (char) mh_cv.invokeExact()); fail(); } catch (NullPointerException _) {} // void -> char + try { assertEquals((short) 0, (short) mh_sv.invokeExact()); fail(); } catch (NullPointerException _) {} // void -> short + try { assertEquals( 0, (int) mh_iv.invokeExact()); fail(); } catch (NullPointerException _) {} // void -> int + try { assertEquals( 0L, (long) mh_jv.invokeExact()); fail(); } catch (NullPointerException _) {} // void -> long + try { assertEquals( 0.0f, (float) mh_fv.invokeExact()); fail(); } catch (NullPointerException _) {} // void -> float + try { assertEquals( 0.0d, (double) mh_dv.invokeExact()); fail(); } catch (NullPointerException _) {} // void -> double + } + + private static MethodHandle mh_v(Class arg) { return mh(void.class, arg); } + + private final static MethodHandle mh_vz = mh_v(boolean.class); + private final static MethodHandle mh_vb = mh_v(byte.class ); + private final static MethodHandle mh_vc = mh_v(char.class ); + private final static MethodHandle mh_vs = mh_v(short.class ); + private final static MethodHandle mh_vi = mh_v(int.class ); + private final static MethodHandle mh_vj = mh_v(long.class ); + private final static MethodHandle mh_vf = mh_v(float.class ); + private final static MethodHandle mh_vd = mh_v(double.class ); + + private static void prim2void(int x) throws Throwable { + boolean z = ((x & 1) != 0); + mh_vz.invokeExact( z); // boolean -> void + mh_vb.invokeExact((byte) x); // byte -> void + mh_vc.invokeExact((char) x); // char -> void + mh_vs.invokeExact((short) x); // short -> void + mh_vi.invokeExact((int) x); // int -> void + mh_vj.invokeExact((long) x); // long -> void + mh_vf.invokeExact((float) x); // float -> void + mh_vd.invokeExact((double) x); // double -> void + } + + private static void prim2void_invalid(int x) throws Throwable { + // no cases + } + + private static boolean identity(boolean v) { return v; } + private static byte identity(byte v) { return v; } + private static char identity(char v) { return v; } + private static short identity(short v) { return v; } + private static int identity(int v) { return v; } + private static long identity(long v) { return v; } + private static float identity(float v) { return v; } + private static double identity(double v) { return v; } + private static void identity() {} +} diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/test/java/lang/invoke/InvokeGenericTest.java --- a/jdk/test/java/lang/invoke/InvokeGenericTest.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/test/java/lang/invoke/InvokeGenericTest.java Tue May 17 14:29:59 2011 -0700 @@ -24,7 +24,7 @@ */ /* @test - * @summary unit tests for java.lang.invoke.MethodHandle.invokeGeneric + * @summary unit tests for java.lang.invoke.MethodHandle.invoke * @compile -target 7 InvokeGenericTest.java * @run junit/othervm test.java.lang.invoke.InvokeGenericTest */ @@ -53,6 +53,10 @@ if (vstr != null) verbosity = Integer.parseInt(vstr); } + public static void main(String... av) throws Throwable { + new InvokeGenericTest().testFirst(); + } + @Test public void testFirst() throws Throwable { verbosity += 9; try { @@ -103,7 +107,7 @@ void startTest(String name) { if (testName != null) printCounts(); if (verbosity >= 1) - System.out.println(name); + System.out.println("["+name+"]"); posTests = negTests = 0; testName = name; } @@ -350,6 +354,30 @@ String[] args = { "one", "two" }; MethodHandle mh = callable(Object.class, String.class); Object res; List resl; + res = resl = (List) mh.invoke((String)args[0], (Object)args[1]); + //System.out.println(res); + assertEquals(Arrays.asList(args), res); + } + + @Test + public void testSimplePrims() throws Throwable { + startTest("testSimplePrims"); + countTest(); + int[] args = { 1, 2 }; + MethodHandle mh = callable(Object.class, Object.class); + Object res; List resl; + res = resl = (List) mh.invoke(args[0], args[1]); + //System.out.println(res); + assertEquals(Arrays.toString(args), res.toString()); + } + + @Test + public void testAlternateName() throws Throwable { + startTest("testAlternateName"); + countTest(); + String[] args = { "one", "two" }; + MethodHandle mh = callable(Object.class, String.class); + Object res; List resl; res = resl = (List) mh.invokeGeneric((String)args[0], (Object)args[1]); //System.out.println(res); assertEquals(Arrays.asList(args), res); @@ -388,24 +416,24 @@ try { switch (args.length) { case 0: - junk = target.invokeGeneric(); break; + junk = target.invoke(); break; case 1: - junk = target.invokeGeneric(args[0]); break; + junk = target.invoke(args[0]); break; case 2: - junk = target.invokeGeneric(args[0], args[1]); break; + junk = target.invoke(args[0], args[1]); break; case 3: - junk = target.invokeGeneric(args[0], args[1], args[2]); break; + junk = target.invoke(args[0], args[1], args[2]); break; case 4: - junk = target.invokeGeneric(args[0], args[1], args[2], args[3]); break; + junk = target.invoke(args[0], args[1], args[2], args[3]); break; default: junk = target.invokeWithArguments(args); break; } } catch (WrongMethodTypeException ex) { return; } catch (Exception ex) { - throw new RuntimeException("wrong exception calling "+target+target.type()+" on "+Arrays.asList(args)+" : "+ex); + throw new RuntimeException("wrong exception calling "+target+" on "+Arrays.asList(args), ex); } - throw new RuntimeException("bad success calling "+target+target.type()+" on "+Arrays.asList(args)); + throw new RuntimeException("bad success calling "+target+" on "+Arrays.asList(args)); } /** Make a list of all combinations of the given types, with the given arities. @@ -451,7 +479,7 @@ startTest("testReferenceConversions"); toString_MH = LOOKUP. findVirtual(Object.class, "toString", MethodType.methodType(String.class)); - String[] args = { "one", "two" }; + Object[] args = { "one", "two" }; for (MethodType type : allMethodTypes(2, Object.class, String.class, RandomInterface.class)) { testReferenceConversions(type, args); } @@ -463,7 +491,7 @@ MethodHandle tsdrop = MethodHandles.dropArguments(toString_MH, 1, type.parameterList()); mh = MethodHandles.foldArguments(tsdrop, mh); mh = mh.asType(type); - Object res = mh.invokeGeneric((String)args[0], (Object)args[1]); + Object res = mh.invoke((String)args[0], (Object)args[1]); //System.out.println(res); assertEquals(Arrays.asList(args).toString(), res); } @@ -473,10 +501,10 @@ public void testBoxConversions() throws Throwable { startTest("testBoxConversions"); countTest(); - Integer[] args = { 1, 2 }; + Object[] args = { 1, 2 }; MethodHandle mh = callable(Object.class, int.class); Object res; List resl; - res = resl = (List) mh.invokeGeneric((int)args[0], (Object)args[1]); + res = resl = (List) mh.invoke((int)args[0], (Object)args[1]); //System.out.println(res); assertEquals(Arrays.asList(args), res); } diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/test/java/lang/invoke/JavaDocExamplesTest.java --- a/jdk/test/java/lang/invoke/JavaDocExamplesTest.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/test/java/lang/invoke/JavaDocExamplesTest.java Tue May 17 14:29:59 2011 -0700 @@ -170,8 +170,8 @@ mt = MethodType.methodType(java.util.List.class, Object[].class); mh = lookup.findStatic(java.util.Arrays.class, "asList", mt); assert(mh.isVarargsCollector()); -x = mh.invokeGeneric("one", "two"); -// invokeGeneric(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; +x = mh.invoke("one", "two"); +// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; assert(x.equals(java.util.Arrays.asList("one","two"))); // mt is (Object,Object,Object)Object mt = MethodType.genericMethodType(3); @@ -199,12 +199,12 @@ MethodHandle asList = publicLookup() .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class)) .asVarargsCollector(Object[].class); -assertEquals("[]", asList.invokeGeneric().toString()); -assertEquals("[1]", asList.invokeGeneric(1).toString()); -assertEquals("[two, too]", asList.invokeGeneric("two", "too").toString()); +assertEquals("[]", asList.invoke().toString()); +assertEquals("[1]", asList.invoke(1).toString()); +assertEquals("[two, too]", asList.invoke("two", "too").toString()); Object[] argv = { "three", "thee", "tee" }; -assertEquals("[three, thee, tee]", asList.invokeGeneric(argv).toString()); -List ls = (List) asList.invokeGeneric((Object)argv); +assertEquals("[three, thee, tee]", asList.invoke(argv).toString()); +List ls = (List) asList.invoke((Object)argv); assertEquals(1, ls.size()); assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0))); }} @@ -218,9 +218,9 @@ .asVarargsCollector(Object[].class); MethodHandle mh = MethodHandles.exactInvoker(vamh.type()).bindTo(vamh); assert(vamh.type().equals(mh.type())); -assertEquals("[1, 2, 3]", vamh.invokeGeneric(1,2,3).toString()); +assertEquals("[1, 2, 3]", vamh.invoke(1,2,3).toString()); boolean failed = false; -try { mh.invokeGeneric(1,2,3); } +try { mh.invoke(1,2,3); } catch (WrongMethodTypeException ex) { failed = true; } assert(failed); {} diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/test/java/lang/invoke/MethodHandlesTest.java --- a/jdk/test/java/lang/invoke/MethodHandlesTest.java Thu May 12 17:17:36 2011 -0700 +++ b/jdk/test/java/lang/invoke/MethodHandlesTest.java Tue May 17 14:29:59 2011 -0700 @@ -105,24 +105,6 @@ public MethodHandlesTest() { } - @Before - public void checkImplementedPlatform() { - boolean platformOK = false; - Properties properties = System.getProperties(); - String vers = properties.getProperty("java.vm.version"); - String name = properties.getProperty("java.vm.name"); - String arch = properties.getProperty("os.arch"); - if ((arch.equals("amd64") || arch.equals("i386") || arch.equals("x86") || - arch.equals("sparc") || arch.equals("sparcv9")) && - (name.contains("Client") || name.contains("Server")) - ) { - platformOK = true; - } else { - System.err.println("Skipping tests for unsupported platform: "+Arrays.asList(vers, name, arch)); - } - assumeTrue(platformOK); - } - String testName; static int allPosTests, allNegTests; int posTests, negTests; @@ -331,6 +313,9 @@ static MethodHandle varargsArray(int arity) { return ValueConversions.varargsArray(arity); } + static MethodHandle varargsArray(Class arrayType, int arity) { + return ValueConversions.varargsArray(arrayType, arity); + } /** Variation of varargsList, but with the given rtype. */ static MethodHandle varargsList(int arity, Class rtype) { MethodHandle list = varargsList(arity); @@ -865,7 +850,7 @@ Class type = (Class) t[1]; Object value; Field field; - try { + try { field = HasFields.class.getDeclaredField(name); } catch (Exception ex) { throw new InternalError("no field HasFields."+name); @@ -1144,16 +1129,9 @@ : MethodHandles.arrayElementSetter(arrayType); assertSame(mh.type(), expType); if (elemType != int.class && elemType != boolean.class) { - MethodType gtype; - if (true) { // FIXME: remove this path (and remove below in the mh.invokes) - gtype = mh.type().changeParameterType(0, Object.class); - if (testSetter) - gtype = gtype.changeParameterType(2, Object.class); - else - gtype = gtype.changeReturnType(Object.class); - } else - // FIXME: This simpler path hits a bug in convertArguments => ToGeneric - gtype = mh.type().generic().changeParameterType(1, int.class); + // FIXME: change Integer.class and (Integer) below to int.class and (int) below. + MethodType gtype = mh.type().generic().changeParameterType(1, Integer.class); + if (testSetter) gtype = gtype.changeReturnType(void.class); mh = MethodHandles.convertArguments(mh, gtype); } Object sawValue, expValue; @@ -1169,7 +1147,7 @@ else if (elemType == boolean.class) mh.invokeExact((boolean[]) array, i, (boolean)random); else - mh.invokeExact(array, i, random); + mh.invokeExact(array, (Integer)i, random); assertEquals(model, array2list(array)); } else { Array.set(array, i, random); @@ -1189,7 +1167,7 @@ else if (elemType == boolean.class) sawValue = (boolean) mh.invokeExact((boolean[]) array, i); else - sawValue = mh.invokeExact(array, i); + sawValue = mh.invokeExact(array, (Integer)i); assertEquals(sawValue, expValue); assertEquals(model, array2list(array)); } @@ -1341,21 +1319,15 @@ int numcases = 1; for (int outargs = 0; outargs <= max; outargs++) { if (outargs - inargs >= MAX_ARG_INCREASE) continue; - int[] reorder = new int[outargs]; int casStep = dilution + 1; // Avoid some common factors: while ((casStep > 2 && casStep % 2 == 0 && inargs % 2 == 0) || (casStep > 3 && casStep % 3 == 0 && inargs % 3 == 0)) casStep++; - for (int cas = 0; cas < numcases; cas += casStep) { - for (int i = 0, c = cas; i < outargs; i++) { - reorder[i] = c % inargs; - c /= inargs; - } - testPermuteArguments(args, types, reorder); - } + testPermuteArguments(args, types, outargs, numcases, casStep); numcases *= inargs; if (dilution > 10 && outargs >= 4) { + int[] reorder = new int[outargs]; // Do some special patterns, which we probably missed. // Replication of a single argument or argument pair. for (int i = 0; i < inargs; i++) { @@ -1383,6 +1355,19 @@ } } + public void testPermuteArguments(Object[] args, Class[] types, + int outargs, int numcases, int casStep) throws Throwable { + int inargs = args.length; + int[] reorder = new int[outargs]; + for (int cas = 0; cas < numcases; cas += casStep) { + for (int i = 0, c = cas; i < outargs; i++) { + reorder[i] = c % inargs; + c /= inargs; + } + testPermuteArguments(args, types, reorder); + } + } + static int[] reverse(int[] reorder) { reorder = reorder.clone(); for (int i = 0, imax = reorder.length / 2; i < imax; i++) { @@ -1433,6 +1418,12 @@ MethodHandle newTarget = MethodHandles.permuteArguments(target, inType, reorder); Object result = newTarget.invokeWithArguments(args); Object expected = Arrays.asList(permArgs); + if (!expected.equals(result)) { + System.out.println("*** failed permuteArguments "+Arrays.toString(reorder)+" types="+Arrays.asList(types)); + System.out.println("in args: "+Arrays.asList(args)); + System.out.println("out args: "+expected); + System.out.println("bad args: "+result); + } assertEquals(expected, result); } @@ -1456,26 +1447,27 @@ } public void testSpreadArguments(Class argType, int pos, int nargs) throws Throwable { countTest(); - MethodHandle target = varargsArray(nargs); - MethodHandle target2 = changeArgTypes(target, argType); + Class arrayType = java.lang.reflect.Array.newInstance(argType, 0).getClass(); + MethodHandle target2 = varargsArray(arrayType, nargs); + MethodHandle target = target2.asType(target2.type().generic()); if (verbosity >= 3) System.out.println("spread into "+target2+" ["+pos+".."+nargs+"]"); Object[] args = randomArgs(target2.type().parameterArray()); // make sure the target does what we think it does: - if (pos == 0 && nargs < 5) { - Object[] check = (Object[]) target.invokeWithArguments(args); + if (pos == 0 && nargs < 5 && !argType.isPrimitive()) { + Object[] check = (Object[]) (Object) target.invokeWithArguments(args); assertArrayEquals(args, check); switch (nargs) { case 0: - check = (Object[]) target.invokeExact(); + check = (Object[]) (Object) target.invokeExact(); assertArrayEquals(args, check); break; case 1: - check = (Object[]) target.invokeExact(args[0]); + check = (Object[]) (Object) target.invokeExact(args[0]); assertArrayEquals(args, check); break; case 2: - check = (Object[]) target.invokeExact(args[0], args[1]); + check = (Object[]) (Object) target.invokeExact(args[0], args[1]); assertArrayEquals(args, check); break; } @@ -1483,23 +1475,50 @@ List> newParams = new ArrayList>(target2.type().parameterList()); { // modify newParams in place List> spreadParams = newParams.subList(pos, nargs); - spreadParams.clear(); spreadParams.add(Object[].class); + spreadParams.clear(); spreadParams.add(arrayType); } - MethodType newType = MethodType.methodType(Object.class, newParams); - MethodHandle result = target2.asSpreader(Object[].class, nargs-pos).asType(newType); - Object[] returnValue; + MethodType newType = MethodType.methodType(arrayType, newParams); + MethodHandle result = target2.asSpreader(arrayType, nargs-pos); + assert(result.type() == newType) : Arrays.asList(result, newType); + result = result.asType(newType.generic()); + Object returnValue; if (pos == 0) { - // In the following line, the first cast implies - // normal Object return value for the MH call (Object[])->Object, - // while the second cast dynamically converts to an Object array. - // Such a double cast is typical of MH.invokeExact. - returnValue = (Object[]) (Object) result.invokeExact(args); + Object args2 = ValueConversions.changeArrayType(arrayType, Arrays.copyOfRange(args, pos, args.length)); + returnValue = result.invokeExact(args2); } else { Object[] args1 = Arrays.copyOfRange(args, 0, pos+1); - args1[pos] = Arrays.copyOfRange(args, pos, args.length); - returnValue = (Object[]) result.invokeWithArguments(args1); + args1[pos] = ValueConversions.changeArrayType(arrayType, Arrays.copyOfRange(args, pos, args.length)); + returnValue = result.invokeWithArguments(args1); } - assertArrayEquals(args, returnValue); + String argstr = Arrays.toString(args); + if (!argType.isPrimitive()) { + Object[] rv = (Object[]) returnValue; + String rvs = Arrays.toString(rv); + if (!Arrays.equals(args, rv)) { + System.out.println("method: "+result); + System.out.println("expected: "+argstr); + System.out.println("returned: "+rvs); + assertArrayEquals(args, rv); + } + } else if (argType == int.class) { + String rvs = Arrays.toString((int[]) returnValue); + if (!argstr.equals(rvs)) { + System.out.println("method: "+result); + System.out.println("expected: "+argstr); + System.out.println("returned: "+rvs); + assertEquals(argstr, rvs); + } + } else if (argType == long.class) { + String rvs = Arrays.toString((long[]) returnValue); + if (!argstr.equals(rvs)) { + System.out.println("method: "+result); + System.out.println("expected: "+argstr); + System.out.println("returned: "+rvs); + assertEquals(argstr, rvs); + } + } else { + // cannot test... + } } @Test @@ -1780,7 +1799,7 @@ assertCalled("invokee", args); // generic invoker countTest(); - inv = MethodHandles.genericInvoker(type); + inv = MethodHandles.invoker(type); if (nargs <= 3) { calledLog.clear(); switch (nargs) { @@ -2130,15 +2149,12 @@ Object z = surprise.invokeExact(x); System.out.println("Failed to throw; got z="+z); assertTrue(false); - } catch (Exception ex) { + } catch (ClassCastException ex) { if (verbosity > 2) System.out.println("caught "+ex); if (verbosity > 3) ex.printStackTrace(); - assertTrue(ex instanceof ClassCastException - // FIXME: accept only one of the two for any given unit test - || ex instanceof WrongMethodTypeException - ); + assertTrue(true); // all is well } } @@ -2328,6 +2344,34 @@ // else need to spin bytecode or do something else fancy throw new UnsupportedOperationException("NYI: cannot form a varargs array of length "+nargs); } + public static MethodHandle varargsArray(Class arrayType, int nargs) { + Class elemType = arrayType.getComponentType(); + MethodType vaType = MethodType.methodType(arrayType, Collections.>nCopies(nargs, elemType)); + MethodHandle mh = varargsArray(nargs); + if (arrayType != Object[].class) + mh = MethodHandles.filterReturnValue(mh, CHANGE_ARRAY_TYPE.bindTo(arrayType)); + return mh.asType(vaType); + } + static Object changeArrayType(Class arrayType, Object[] a) { + Class elemType = arrayType.getComponentType(); + if (!elemType.isPrimitive()) + return Arrays.copyOf(a, a.length, arrayType.asSubclass(Object[].class)); + Object b = java.lang.reflect.Array.newInstance(elemType, a.length); + for (int i = 0; i < a.length; i++) + java.lang.reflect.Array.set(b, i, a[i]); + return b; + } + private static final MethodHandle CHANGE_ARRAY_TYPE; + static { + try { + CHANGE_ARRAY_TYPE = IMPL_LOOKUP.findStatic(ValueConversions.class, "changeArrayType", + MethodType.methodType(Object.class, Class.class, Object[].class)); + } catch (NoSuchMethodException | IllegalAccessException ex) { + Error err = new InternalError("uncaught exception"); + err.initCause(ex); + throw err; + } + } private static final List NO_ARGS_LIST = Arrays.asList(NO_ARGS_ARRAY); private static List makeList(Object... args) { return Arrays.asList(args); } diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/test/java/lang/invoke/RicochetTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/lang/invoke/RicochetTest.java Tue May 17 14:29:59 2011 -0700 @@ -0,0 +1,582 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @summary unit tests for recursive method handles + * @run junit/othervm -DRicochetTest.MAX_ARITY=255 test.java.lang.invoke.RicochetTest + */ + +package test.java.lang.invoke; + +import java.lang.invoke.*; +import java.util.*; +import org.junit.*; +import static java.lang.invoke.MethodType.*; +import static java.lang.invoke.MethodHandles.*; +import static org.junit.Assert.*; +import static org.junit.Assume.*; + + +/** + * + * @author jrose + */ +public class RicochetTest { + private static final Class CLASS = RicochetTest.class; + private static final int MAX_ARITY = Integer.getInteger(CLASS.getSimpleName()+".MAX_ARITY", 40); + + public static void main(String... av) throws Throwable { + RicochetTest test = new RicochetTest(); + if (av.length > 0) test.testOnly = Arrays.asList(av).toString(); + if (REPEAT == 1 || test.testOnly != null) { + test.testAll(); + if (test.testOnlyTests == null) throw new RuntimeException("no matching test: "+test.testOnly); + } else if (REPEAT == 0) { + org.junit.runner.JUnitCore.runClasses(RicochetTest.class); + } else { + verbose(1, "REPEAT="+REPEAT); + for (int i = 0; i < REPEAT; i++) { + test.testRepetition = (i+1); + verbose(0, "[#"+test.testRepetition+"]"); + test.testAll(); + } + } + } + int testRepetition; + + public void testAll() throws Throwable { + testNull(); + testBoxInteger(); + testFilterReturnValue(); + testFilterObject(); + testBoxLong(); + testFilterInteger(); + testIntSpreads(); + testByteSpreads(); + testLongSpreads(); + testIntCollects(); + testReturns(); + } + + @Test + public void testNull() throws Throwable { + if (testRepetition > (1+REPEAT/100)) return; // trivial test + if (!startTest("testNull")) return; + assertEquals(opI(37), opI.invokeWithArguments(37)); + assertEqualFunction(opI, opI); + } + + @Test + public void testBoxInteger() throws Throwable { + if (!startTest("testBoxInteger")) return; + assertEqualFunction(opI, opI.asType(opL_I.type()).asType(opI.type())); + } + + @Test + public void testFilterReturnValue() throws Throwable { + if (!startTest("testFilterReturnValue")) return; + int[] ints = { 12, 23, 34, 45, 56, 67, 78, 89 }; + Object res = list8ints.invokeExact(ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7]); + assertEquals(Arrays.toString(ints), res.toString()); + MethodHandle idreturn = filterReturnValue(list8ints, identity(Object.class)); + res = idreturn.invokeExact(ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7]); + assertEquals(Arrays.toString(ints), res.toString()); + MethodHandle add0 = addL.bindTo(0); + assertEqualFunction(filterReturnValue(opL2, add0), opL2); + } + + @Test + public void testFilterObject() throws Throwable { + if (!startTest("testFilterObject")) return; + MethodHandle add0 = addL.bindTo(0); + assertEqualFunction(sequence(opL2, add0), opL2); + int bump13 = -13; // value near 20 works as long as test values are near [-80..80] + MethodHandle add13 = addL.bindTo(bump13); + MethodHandle add13_0 = addL.bindTo(opI2(bump13, 0)); + MethodHandle add13_1 = addL.bindTo(opI2(0, bump13)); + assertEqualFunction(sequence(opL2, add13_0), + filterArguments(opL2, 0, add13)); + assertEqualFunction(sequence(opL2, add13_1), + filterArguments(opL2, 1, add13)); + System.out.println("[testFilterObject done]"); + } + + @Test + public void testBoxLong() throws Throwable { + if (!startTest("testBoxLong")) return; + assertEqualFunction(opJ, opJ.asType(opL_J.type()).asType(opJ.type())); + } + + @Test + public void testFilterInteger() throws Throwable { + if (!startTest("testFilterInteger")) return; + assertEqualFunction(opI, sequence(convI_L, opL_I)); + } + + @Test + public void testIntSpreads() throws Throwable { + if (!startTest("testIntSpreads")) return; + MethodHandle id = identity(int[].class); + final int MAX = MAX_ARITY-2; // 253+1 would cause parameter overflow with 'this' added + for (int nargs = 0; nargs <= MAX; nargs++) { + if (nargs > 30 && nargs < MAX-20) nargs += 10; + int[] args = new int[nargs]; + for (int j = 0; j < args.length; j++) args[j] = (int)(j + 11); + //System.out.println("testIntSpreads "+Arrays.toString(args)); + int[] args1 = (int[]) id.invokeExact(args); + assertArrayEquals(args, args1); + MethodHandle coll = id.asCollector(int[].class, nargs); + int[] args2 = args; + switch (nargs) { + case 0: args2 = (int[]) coll.invokeExact(); break; + case 1: args2 = (int[]) coll.invokeExact(args[0]); break; + case 2: args2 = (int[]) coll.invokeExact(args[0], args[1]); break; + case 3: args2 = (int[]) coll.invokeExact(args[0], args[1], args[2]); break; + case 4: args2 = (int[]) coll.invokeExact(args[0], args[1], args[2], args[3]); break; + case 5: args2 = (int[]) coll.invokeExact(args[0], args[1], args[2], args[3], args[4]); break; + } + assertArrayEquals(args, args2); + MethodHandle mh = coll.asSpreader(int[].class, nargs); + int[] args3 = (int[]) mh.invokeExact(args); + assertArrayEquals(args, args3); + } + } + + @Test + public void testByteSpreads() throws Throwable { + if (!startTest("testByteSpreads")) return; + MethodHandle id = identity(byte[].class); + final int MAX = MAX_ARITY-2; // 253+1 would cause parameter overflow with 'this' added + for (int nargs = 0; nargs <= MAX; nargs++) { + if (nargs > 30 && nargs < MAX-20) nargs += 10; + byte[] args = new byte[nargs]; + for (int j = 0; j < args.length; j++) args[j] = (byte)(j + 11); + //System.out.println("testByteSpreads "+Arrays.toString(args)); + byte[] args1 = (byte[]) id.invokeExact(args); + assertArrayEquals(args, args1); + MethodHandle coll = id.asCollector(byte[].class, nargs); + byte[] args2 = args; + switch (nargs) { + case 0: args2 = (byte[]) coll.invokeExact(); break; + case 1: args2 = (byte[]) coll.invokeExact(args[0]); break; + case 2: args2 = (byte[]) coll.invokeExact(args[0], args[1]); break; + case 3: args2 = (byte[]) coll.invokeExact(args[0], args[1], args[2]); break; + case 4: args2 = (byte[]) coll.invokeExact(args[0], args[1], args[2], args[3]); break; + case 5: args2 = (byte[]) coll.invokeExact(args[0], args[1], args[2], args[3], args[4]); break; + } + assertArrayEquals(args, args2); + MethodHandle mh = coll.asSpreader(byte[].class, nargs); + byte[] args3 = (byte[]) mh.invokeExact(args); + assertArrayEquals(args, args3); + } + } + + @Test + public void testLongSpreads() throws Throwable { + if (!startTest("testLongSpreads")) return; + MethodHandle id = identity(long[].class); + final int MAX = (MAX_ARITY - 2) / 2; // 253/2+1 would cause parameter overflow with 'this' added + for (int nargs = 0; nargs <= MAX; nargs++) { + if (nargs > 30 && nargs < MAX-20) nargs += 10; + long[] args = new long[nargs]; + for (int j = 0; j < args.length; j++) args[j] = (long)(j + 11); + //System.out.println("testLongSpreads "+Arrays.toString(args)); + long[] args1 = (long[]) id.invokeExact(args); + assertArrayEquals(args, args1); + MethodHandle coll = id.asCollector(long[].class, nargs); + long[] args2 = args; + switch (nargs) { + case 0: args2 = (long[]) coll.invokeExact(); break; + case 1: args2 = (long[]) coll.invokeExact(args[0]); break; + case 2: args2 = (long[]) coll.invokeExact(args[0], args[1]); break; + case 3: args2 = (long[]) coll.invokeExact(args[0], args[1], args[2]); break; + case 4: args2 = (long[]) coll.invokeExact(args[0], args[1], args[2], args[3]); break; + case 5: args2 = (long[]) coll.invokeExact(args[0], args[1], args[2], args[3], args[4]); break; + } + assertArrayEquals(args, args2); + MethodHandle mh = coll.asSpreader(long[].class, nargs); + long[] args3 = (long[]) mh.invokeExact(args); + assertArrayEquals(args, args3); + } + } + + @Test + public void testIntCollects() throws Throwable { + if (!startTest("testIntCollects")) return; + for (MethodHandle lister : INT_LISTERS) { + int outputs = lister.type().parameterCount(); + for (int collects = 0; collects <= Math.min(outputs, INT_COLLECTORS.length-1); collects++) { + int inputs = outputs - 1 + collects; + if (inputs < 0) continue; + for (int pos = 0; pos + collects <= inputs; pos++) { + MethodHandle collector = INT_COLLECTORS[collects]; + int[] args = new int[inputs]; + int ap = 0, arg = 31; + for (int i = 0; i < pos; i++) + args[ap++] = arg++ + 0; + for (int i = 0; i < collects; i++) + args[ap++] = arg++ + 10; + while (ap < args.length) + args[ap++] = arg++ + 20; + // calculate piecemeal: + //System.out.println("testIntCollects "+Arrays.asList(lister, pos, collector)+" on "+Arrays.toString(args)); + int[] collargs = Arrays.copyOfRange(args, pos, pos+collects); + int coll = (int) collector.asSpreader(int[].class, collargs.length).invokeExact(collargs); + int[] listargs = Arrays.copyOfRange(args, 0, outputs); + System.arraycopy(args, pos+collects, listargs, pos+1, outputs - (pos+1)); + listargs[pos] = coll; + //System.out.println(" coll="+coll+" listargs="+Arrays.toString(listargs)); + Object expect = lister.asSpreader(int[].class, listargs.length).invokeExact(listargs); + //System.out.println(" expect="+expect); + + // now use the combined MH, and test the output: + MethodHandle mh = collectArguments(lister, pos, INT_COLLECTORS[collects]); + if (mh == null) continue; // no infix collection, yet + assert(mh.type().parameterCount() == inputs); + Object observe = mh.asSpreader(int[].class, args.length).invokeExact(args); + assertEquals(expect, observe); + } + } + } + } + + private static MethodHandle collectArguments(MethodHandle lister, int pos, MethodHandle collector) { + int collects = collector.type().parameterCount(); + int outputs = lister.type().parameterCount(); + if (pos == outputs - 1) + return MethodHandles.filterArguments(lister, pos, + collector.asSpreader(int[].class, collects)) + .asCollector(int[].class, collects); + //return MethodHandles.collectArguments(lister, pos, collector); //no such animal + return null; + } + + private static final Class[] RETURN_TYPES = { + Object.class, String.class, Integer.class, + int.class, long.class, + boolean.class, byte.class, char.class, short.class, + float.class, double.class, + void.class, + }; + + @Test + public void testReturns() throws Throwable { + if (!startTest("testReturns")) return; + // fault injection: + int faultCount = 0; // total of 1296 tests + faultCount = Integer.getInteger("testReturns.faultCount", 0); + for (Class ret : RETURN_TYPES) { + // make a complicated identity function and pass something through it + System.out.println(ret.getSimpleName()); + Class vret = (ret == void.class) ? Void.class : ret; + MethodHandle id = // (vret)->ret + identity(vret).asType(methodType(ret, vret)); + final int LENGTH = 4; + int[] index = {0}; + Object vals = java.lang.reflect.Array.newInstance(vret, LENGTH); + MethodHandle indexGetter = //()->int + insertArguments(arrayElementGetter(index.getClass()), 0, index, 0); + MethodHandle valSelector = // (int)->vret + arrayElementGetter(vals.getClass()).bindTo(vals); + MethodHandle valGetter = // ()->vret + foldArguments(valSelector, indexGetter); + if (ret != void.class) { + for (int i = 0; i < LENGTH; i++) { + Object val = (i + 50); + if (ret == boolean.class) val = (i % 3 == 0); + if (ret == String.class) val = "#"+i; + if (ret == char.class) val = (char)('a'+i); + if (ret == byte.class) val = (byte)~i; + if (ret == short.class) val = (short)(1< "+val); + index[0] = i; + if (--faultCount == 0) index[0] ^= 1; + Object x = valGetter.invokeWithArguments(); + assertEquals(val, x); + // make a return-filter call: x = id(valGetter()) + if (--faultCount == 0) index[0] ^= 1; + x = filterReturnValue(valGetter, id).invokeWithArguments(); + assertEquals(val, x); + // make a filter call: x = id(*,valGetter(),*) + for (int len = 1; len <= 4; len++) { + for (int pos = 0; pos < len; pos++) { + MethodHandle proj = id; // lambda(..., vret x,...){x} + for (int j = 0; j < len; j++) { + if (j == pos) continue; + proj = dropArguments(proj, j, Object.class); + } + assert(proj.type().parameterCount() == len); + // proj: (Object*, pos: vret, Object*)->ret + assertEquals(vret, proj.type().parameterType(pos)); + MethodHandle vgFilter = dropArguments(valGetter, 0, Object.class); + if (--faultCount == 0) index[0] ^= 1; + x = filterArguments(proj, pos, vgFilter).invokeWithArguments(new Object[len]); + assertEquals(val, x); + } + } + // make a fold call: + for (int len = 0; len <= 4; len++) { + for (int fold = 0; fold <= len; fold++) { + MethodHandle proj = id; // lambda(ret x, ...){x} + if (ret == void.class) proj = constant(Object.class, null); + int arg0 = (ret == void.class ? 0 : 1); + for (int j = 0; j < len; j++) { + proj = dropArguments(proj, arg0, Object.class); + } + assert(proj.type().parameterCount() == arg0 + len); + // proj: (Object*, pos: vret, Object*)->ret + if (arg0 != 0) assertEquals(vret, proj.type().parameterType(0)); + MethodHandle vgFilter = valGetter.asType(methodType(ret)); + for (int j = 0; j < fold; j++) { + vgFilter = dropArguments(vgFilter, j, Object.class); + } + x = foldArguments(proj, vgFilter).invokeWithArguments(new Object[len]); + if (--faultCount == 0) index[0] ^= 1; + assertEquals(val, x); + } + } + } + } + //System.out.println("faultCount="+faultCount); + } + + private static MethodHandle sequence(MethodHandle mh1, MethodHandle... mhs) { + MethodHandle res = mh1; + for (MethodHandle mh2 : mhs) + res = filterReturnValue(res, mh2); + return res; + } + private static void assertEqualFunction(MethodHandle x, MethodHandle y) throws Throwable { + assertEquals(x.type(), y.type()); //?? + MethodType t = x.type(); + if (t.parameterCount() == 0) { + assertEqualFunctionAt(null, x, y); + return; + } + Class ptype = t.parameterType(0); + if (ptype == long.class || ptype == Long.class) { + for (long i = -10; i <= 10; i++) { + assertEqualFunctionAt(i, x, y); + } + } else { + for (int i = -10; i <= 10; i++) { + assertEqualFunctionAt(i, x, y); + } + } + } + private static void assertEqualFunctionAt(Object v, MethodHandle x, MethodHandle y) throws Throwable { + Object[] args = new Object[x.type().parameterCount()]; + Arrays.fill(args, v); + Object xval = invokeWithCatch(x, args); + Object yval = invokeWithCatch(y, args); + String msg = "ok"; + if (!Objects.equals(xval, yval)) { + msg = ("applying "+x+" & "+y+" to "+v); + } + assertEquals(msg, xval, yval); + } + private static Object invokeWithCatch(MethodHandle mh, Object... args) throws Throwable { + try { + return mh.invokeWithArguments(args); + } catch (Throwable ex) { + System.out.println("threw: "+mh+Arrays.asList(args)); + ex.printStackTrace(); + return ex; + } + } + + private static final Lookup LOOKUP = lookup(); + private static MethodHandle findStatic(String name, + Class rtype, + Class... ptypes) { + try { + return LOOKUP.findStatic(LOOKUP.lookupClass(), name, methodType(rtype, ptypes)); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException(ex); + } + } + private static MethodHandle findStatic(String name, + Class rtype, + List ptypes) { + return findStatic(name, rtype, ptypes.toArray(new Class[ptypes.size()])); + } + static int getProperty(String name, int dflt) { + String qual = LOOKUP.lookupClass().getName(); + String prop = System.getProperty(qual+"."+name); + if (prop == null) prop = System.getProperty(name); + if (prop == null) return dflt; + return Integer.parseInt(prop); + } + + private static int opI(int... xs) { + stress(); + int base = 100; + int z = 0; + for (int x : xs) { + z = (z * base) + (x % base); + } + verbose("opI", xs.length, xs, z); + return z; + } + private static int opI2(int x, int y) { return opI(x, y); } // x*100 + y%100 + private static int opI3(int x, int y, int z) { return opI(x, y, z); } + private static int opI4(int w, int x, int y, int z) { return opI(w, x, y, z); } + private static int opI(int x) { return opI2(x, 37); } + private static Object opI_L(int x) { return (Object) opI(x); } + private static long opJ3(long x, long y, long z) { return (long) opI3((int)x, (int)y, (int)z); } + private static long opJ2(long x, long y) { return (long) opI2((int)x, (int)y); } + private static long opJ(long x) { return (long) opI((int)x); } + private static Object opL2(Object x, Object y) { return (Object) opI2((int)x, (int)y); } + private static Object opL(Object x) { return (Object) opI((int)x); } + private static int opL2_I(Object x, Object y) { return (int) opI2((int)x, (int)y); } + private static int opL_I(Object x) { return (int) opI((int)x); } + private static long opL_J(Object x) { return (long) opI((int)x); } + private static final MethodHandle opI, opI2, opI3, opI4, opI_L, opJ, opJ2, opJ3, opL2, opL, opL2_I, opL_I, opL_J; + static { + opI4 = findStatic("opI4", int.class, int.class, int.class, int.class, int.class); + opI3 = findStatic("opI3", int.class, int.class, int.class, int.class); + opI2 = findStatic("opI2", int.class, int.class, int.class); + opI = findStatic("opI", int.class, int.class); + opI_L = findStatic("opI_L", Object.class, int.class); + opJ = findStatic("opJ", long.class, long.class); + opJ2 = findStatic("opJ2", long.class, long.class, long.class); + opJ3 = findStatic("opJ3", long.class, long.class, long.class, long.class); + opL2 = findStatic("opL2", Object.class, Object.class, Object.class); + opL = findStatic("opL", Object.class, Object.class); + opL2_I = findStatic("opL2_I", int.class, Object.class, Object.class); + opL_I = findStatic("opL_I", int.class, Object.class); + opL_J = findStatic("opL_J", long.class, Object.class); + } + private static final MethodHandle[] INT_COLLECTORS = { + constant(int.class, 42), opI, opI2, opI3, opI4 + }; + private static final MethodHandle[] LONG_COLLECTORS = { + constant(long.class, 42), opJ, opJ2, opJ3 + }; + + private static int addI(int x, int y) { stress(); return x+y; } + private static Object addL(Object x, Object y) { return addI((int)x, (int)y); } + private static final MethodHandle addI, addL; + static { + addI = findStatic("addI", int.class, int.class, int.class); + addL = findStatic("addL", Object.class, Object.class, Object.class); + } + + private static Object list8ints(int a, int b, int c, int d, int e, int f, int g, int h) { + return Arrays.asList(a, b, c, d, e, f, g, h); + } + private static Object list8longs(long a, long b, long c, long d, long e, long f, long g, long h) { + return Arrays.asList(a, b, c, d, e, f, g, h); + } + private static final MethodHandle list8ints = findStatic("list8ints", Object.class, + Collections.nCopies(8, int.class)); + private static final MethodHandle list8longs = findStatic("list8longs", Object.class, + Collections.nCopies(8, long.class)); + private static final MethodHandle[] INT_LISTERS, LONG_LISTERS; + static { + int listerCount = list8ints.type().parameterCount() + 1; + INT_LISTERS = new MethodHandle[listerCount]; + LONG_LISTERS = new MethodHandle[listerCount]; + MethodHandle lister = list8ints; + MethodHandle llister = list8longs; + for (int i = listerCount - 1; ; i--) { + INT_LISTERS[i] = lister; + LONG_LISTERS[i] = llister; + if (i == 0) break; + lister = insertArguments(lister, i-1, (int)0); + llister = insertArguments(llister, i-1, (long)0); + } + } + + private static Object convI_L(int x) { stress(); return (Object) x; } + private static int convL_I(Object x) { stress(); return (int) x; } + private static Object convJ_L(long x) { stress(); return (Object) x; } + private static long convL_J(Object x) { stress(); return (long) x; } + private static int convJ_I(long x) { stress(); return (int) x; } + private static long convI_J(int x) { stress(); return (long) x; } + private static final MethodHandle convI_L, convL_I, convJ_L, convL_J, convJ_I, convI_J; + static { + convI_L = findStatic("convI_L", Object.class, int.class); + convL_I = findStatic("convL_I", int.class, Object.class); + convJ_L = findStatic("convJ_L", Object.class, long.class); + convL_J = findStatic("convL_J", long.class, Object.class); + convJ_I = findStatic("convJ_I", int.class, long.class); + convI_J = findStatic("convI_J", long.class, int.class); + } + + // stress modes: + private static final int REPEAT = getProperty("REPEAT", 0); + private static final int STRESS = getProperty("STRESS", 0); + private static /*v*/ int STRESS_COUNT; + private static final Object[] SINK = new Object[4]; + private static void stress() { + if (STRESS <= 0) return; + int count = STRESS + (STRESS_COUNT++ & 0x1); // non-constant value + for (int i = 0; i < count; i++) { + SINK[i % SINK.length] = new Object[STRESS + i % (SINK.length + 1)]; + } + } + + // verbosity: + private static final int VERBOSITY = getProperty("VERBOSITY", 0) + (REPEAT == 0 ? 0 : -1); + private static void verbose(Object a, Object b, Object c, Object d) { + if (VERBOSITY <= 0) return; + verbose(1, a, b, c, d); + } + private static void verbose(Object a, Object b, Object c) { + if (VERBOSITY <= 0) return; + verbose(1, a, b, c); + } + private static void verbose(int level, Object a, Object... bcd) { + if (level > VERBOSITY) return; + String m = a.toString(); + if (bcd != null && bcd.length > 0) { + List l = new ArrayList<>(bcd.length); + for (Object x : bcd) { + if (x instanceof Object[]) x = Arrays.asList((Object[])x); + if (x instanceof int[]) x = Arrays.toString((int[])x); + if (x instanceof long[]) x = Arrays.toString((long[])x); + l.add(x); + } + m = m+Arrays.asList(bcd); + } + System.out.println(m); + } + String testOnly; + String testOnlyTests; + private boolean startTest(String name) { + if (testOnly != null && !testOnly.contains(name)) + return false; + verbose(0, "["+name+"]"); + testOnlyTests = (testOnlyTests == null) ? name : testOnlyTests+" "+name; + return true; + } + +} diff -r 04f88d98efe3 -r 9fc3d991dc63 jdk/test/sun/invoke/util/ValueConversionsTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/sun/invoke/util/ValueConversionsTest.java Tue May 17 14:29:59 2011 -0700 @@ -0,0 +1,448 @@ +/* + * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package test.sun.invoke.util; + +import sun.invoke.util.ValueConversions; +import sun.invoke.util.Wrapper; +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import org.junit.Ignore; +import org.junit.Test; +import static org.junit.Assert.*; + +/* @test + * @summary unit tests for value-type conversion utilities + * @ignore This test requires a special compilation environment to access sun.inovke.util. Run by hand. + * @run junit/othervm test.sun.invoke.util.ValueConversionsTest + * @run junit/othervm + * -DValueConversionsTest.MAX_ARITY=255 -DValueConversionsTest.START_ARITY=250 + * test.sun.invoke.util.ValueConversionsTest + */ + +// This might take a while and burn lots of metadata: +// @run junit/othervm -DValueConversionsTest.MAX_ARITY=255 -DValueConversionsTest.EXHAUSTIVE=true test.sun.invoke.util.ValueConversionsTest + +/** + * + * @author jrose + */ +public class ValueConversionsTest { + private static final Class CLASS = ValueConversionsTest.class; + private static final int MAX_ARITY = Integer.getInteger(CLASS.getSimpleName()+".MAX_ARITY", 40); + private static final int START_ARITY = Integer.getInteger(CLASS.getSimpleName()+".START_ARITY", 0); + private static final boolean EXHAUSTIVE = Boolean.getBoolean(CLASS.getSimpleName()+".EXHAUSTIVE"); + + @Test + public void testUnbox() throws Throwable { + testUnbox(false); + } + + @Test + public void testUnboxCast() throws Throwable { + testUnbox(true); + } + + private void testUnbox(boolean doCast) throws Throwable { + //System.out.println("unbox"); + for (Wrapper dst : Wrapper.values()) { + //System.out.println(dst); + for (Wrapper src : Wrapper.values()) { + testUnbox(doCast, dst, src); + } + } + } + + private void testUnbox(boolean doCast, Wrapper dst, Wrapper src) throws Throwable { + boolean expectThrow = !doCast && !dst.isConvertibleFrom(src); + if (dst == Wrapper.OBJECT || src == Wrapper.OBJECT) return; // must have prims + if (dst == Wrapper.OBJECT) + expectThrow = false; // everything (even VOID==null here) converts to OBJECT + try { + for (int n = -5; n < 10; n++) { + Object box = src.wrap(n); + switch (src) { + case VOID: assertEquals(box, null); break; + case OBJECT: box = box.toString(); break; + case SHORT: assertEquals(box.getClass(), Short.class); break; + default: assertEquals(box.getClass(), src.wrapperType()); break; + } + MethodHandle unboxer; + if (doCast) + unboxer = ValueConversions.unboxCast(dst.primitiveType()); + else + unboxer = ValueConversions.unbox(dst.primitiveType()); + Object expResult = (box == null) ? dst.zero() : dst.wrap(box); + Object result = null; + switch (dst) { + case INT: result = (int) unboxer.invokeExact(box); break; + case LONG: result = (long) unboxer.invokeExact(box); break; + case FLOAT: result = (float) unboxer.invokeExact(box); break; + case DOUBLE: result = (double) unboxer.invokeExact(box); break; + case CHAR: result = (char) unboxer.invokeExact(box); break; + case BYTE: result = (byte) unboxer.invokeExact(box); break; + case SHORT: result = (short) unboxer.invokeExact(box); break; + case OBJECT: result = (Object) unboxer.invokeExact(box); break; + case BOOLEAN: result = (boolean) unboxer.invokeExact(box); break; + case VOID: result = null; unboxer.invokeExact(box); break; + } + if (expectThrow) { + expResult = "(need an exception)"; + } + assertEquals("(doCast,expectThrow,dst,src,n,box)="+Arrays.asList(doCast,expectThrow,dst,src,n,box), + expResult, result); + } + } catch (RuntimeException ex) { + if (expectThrow) return; + System.out.println("Unexpected throw for (doCast,expectThrow,dst,src)="+Arrays.asList(doCast,expectThrow,dst,src)); + throw ex; + } + } + + @Test + public void testUnboxRaw() throws Throwable { + //System.out.println("unboxRaw"); + for (Wrapper w : Wrapper.values()) { + if (w == Wrapper.OBJECT) continue; // skip this; no raw form + //System.out.println(w); + for (int n = -5; n < 10; n++) { + Object box = w.wrap(n); + long expResult = w.unwrapRaw(box); + Object box2 = w.wrapRaw(expResult); + assertEquals(box, box2); + MethodHandle unboxer = ValueConversions.unboxRaw(w.primitiveType()); + long result = -1; + switch (w) { + case INT: result = (int) unboxer.invokeExact(box); break; + case LONG: result = (long) unboxer.invokeExact(box); break; + case FLOAT: result = (int) unboxer.invokeExact(box); break; + case DOUBLE: result = (long) unboxer.invokeExact(box); break; + case CHAR: result = (int) unboxer.invokeExact(box); break; + case BYTE: result = (int) unboxer.invokeExact(box); break; + case SHORT: result = (int) unboxer.invokeExact(box); break; + case BOOLEAN: result = (int) unboxer.invokeExact(box); break; + case VOID: result = (int) unboxer.invokeExact(box); break; + } + assertEquals("(w,n,box)="+Arrays.asList(w,n,box), + expResult, result); + } + } + } + + @Test + public void testBox() throws Throwable { + //System.out.println("box"); + for (Wrapper w : Wrapper.values()) { + if (w == Wrapper.VOID) continue; // skip this; no unboxed form + //System.out.println(w); + for (int n = -5; n < 10; n++) { + Object box = w.wrap(n); + MethodHandle boxer = ValueConversions.box(w.primitiveType()); + Object expResult = box; + Object result = null; + switch (w) { + case INT: result = boxer.invokeExact((int)n); break; + case LONG: result = boxer.invokeExact((long)n); break; + case FLOAT: result = boxer.invokeExact((float)n); break; + case DOUBLE: result = boxer.invokeExact((double)n); break; + case CHAR: result = boxer.invokeExact((char)n); break; + case BYTE: result = boxer.invokeExact((byte)n); break; + case SHORT: result = boxer.invokeExact((short)n); break; + case OBJECT: result = boxer.invokeExact((Object)n); break; + case BOOLEAN: result = boxer.invokeExact((n & 1) != 0); break; + } + assertEquals("(dst,src,n,box)="+Arrays.asList(w,w,n,box), + expResult, result); + } + } + } + + @Test + public void testBoxRaw() throws Throwable { + //System.out.println("boxRaw"); + for (Wrapper w : Wrapper.values()) { + if (w == Wrapper.VOID) continue; // skip this; no unboxed form + if (w == Wrapper.OBJECT) continue; // skip this; no raw form + //System.out.println(w); + for (int n = -5; n < 10; n++) { + Object box = w.wrap(n); + long raw = w.unwrapRaw(box); + Object expResult = box; + MethodHandle boxer = ValueConversions.boxRaw(w.primitiveType()); + Object result = null; + switch (w) { + case INT: result = boxer.invokeExact((int)raw); break; + case LONG: result = boxer.invokeExact(raw); break; + case FLOAT: result = boxer.invokeExact((int)raw); break; + case DOUBLE: result = boxer.invokeExact(raw); break; + case CHAR: result = boxer.invokeExact((int)raw); break; + case BYTE: result = boxer.invokeExact((int)raw); break; + case SHORT: result = boxer.invokeExact((int)raw); break; + case BOOLEAN: result = boxer.invokeExact((int)raw); break; + } + assertEquals("(dst,src,n,box)="+Arrays.asList(w,w,n,box), + expResult, result); + } + } + } + + @Test + public void testReboxRaw() throws Throwable { + //System.out.println("reboxRaw"); + for (Wrapper w : Wrapper.values()) { + Wrapper pw = Wrapper.forPrimitiveType(w.rawPrimitiveType()); + if (w == Wrapper.VOID) continue; // skip this; no unboxed form + if (w == Wrapper.OBJECT) continue; // skip this; no raw form + //System.out.println(w); + for (int n = -5; n < 10; n++) { + Object box = w.wrap(n); + Object raw = pw.wrap(w.unwrapRaw(box)); + Object expResult = box; + MethodHandle boxer = ValueConversions.rebox(w.primitiveType()); + Object result = null; + switch (w) { + case INT: result = boxer.invokeExact(raw); break; + case LONG: result = boxer.invokeExact(raw); break; + case FLOAT: result = boxer.invokeExact(raw); break; + case DOUBLE: result = boxer.invokeExact(raw); break; + case CHAR: result = boxer.invokeExact(raw); break; + case BYTE: result = boxer.invokeExact(raw); break; + case SHORT: result = boxer.invokeExact(raw); break; + case BOOLEAN: result = boxer.invokeExact(raw); break; + } + assertEquals("(dst,src,n,box)="+Arrays.asList(w,w,n,box), + expResult, result); + } + } + } + + @Test + public void testCast() throws Throwable { + //System.out.println("cast"); + Class[] types = { Object.class, Serializable.class, String.class, Number.class, Integer.class }; + Object[] objects = { new Object(), Boolean.FALSE, "hello", (Long)12L, (Integer)6 }; + for (Class dst : types) { + MethodHandle caster = ValueConversions.cast(dst); + assertEquals(caster.type(), ValueConversions.identity().type()); + for (Object obj : objects) { + Class src = obj.getClass(); + boolean canCast; + if (dst.isInterface()) { + canCast = true; + } else { + canCast = dst.isAssignableFrom(src); + assertEquals(canCast, dst.isInstance(obj)); + } + //System.out.println("obj="+obj+" <: dst="+dst); + try { + Object result = caster.invokeExact(obj); + if (canCast) + assertEquals(obj, result); + else + assertEquals("cast should not have succeeded", dst, obj); + } catch (ClassCastException ex) { + if (canCast) + throw ex; + } + } + } + } + + @Test + public void testIdentity() throws Throwable { + //System.out.println("identity"); + MethodHandle id = ValueConversions.identity(); + Object expResult = "foo"; + Object result = id.invokeExact(expResult); + // compiler bug: ValueConversions.identity().invokeExact("bar"); + assertEquals(expResult, result); + } + + @Test + public void testVarargsArray() throws Throwable { + //System.out.println("varargsArray"); + final int MIN = START_ARITY; + final int MAX = MAX_ARITY-2; // 253+1 would cause parameter overflow with 'this' added + for (int nargs = MIN; nargs <= MAX; nargs = nextArgCount(nargs, 17, MAX)) { + MethodHandle target = ValueConversions.varargsArray(nargs); + Object[] args = new Object[nargs]; + for (int i = 0; i < nargs; i++) + args[i] = "#"+i; + Object res = target.invokeWithArguments(args); + assertArrayEquals(args, (Object[])res); + } + } + + @Test + public void testVarargsReferenceArray() throws Throwable { + //System.out.println("varargsReferenceArray"); + testTypedVarargsArray(Object[].class); + testTypedVarargsArray(String[].class); + testTypedVarargsArray(Number[].class); + } + + @Test + public void testVarargsPrimitiveArray() throws Throwable { + //System.out.println("varargsPrimitiveArray"); + testTypedVarargsArray(int[].class); + testTypedVarargsArray(long[].class); + testTypedVarargsArray(byte[].class); + testTypedVarargsArray(boolean[].class); + testTypedVarargsArray(short[].class); + testTypedVarargsArray(char[].class); + testTypedVarargsArray(float[].class); + testTypedVarargsArray(double[].class); + } + + private static int nextArgCount(int nargs, int density, int MAX) { + if (EXHAUSTIVE) return nargs + 1; + if (nargs >= MAX) return Integer.MAX_VALUE; + int BOT = 20, TOP = MAX-5; + if (density < 10) { BOT = 10; MAX = TOP-2; } + if (nargs <= BOT || nargs >= TOP) { + ++nargs; + } else { + int bump = Math.max(1, 100 / density); + nargs += bump; + if (nargs > TOP) nargs = TOP; + } + return nargs; + } + + private void testTypedVarargsArray(Class arrayType) throws Throwable { + System.out.println(arrayType.getSimpleName()); + Class elemType = arrayType.getComponentType(); + int MIN = START_ARITY; + int MAX = MAX_ARITY-2; // 253+1 would cause parameter overflow with 'this' added + int density = 3; + if (elemType == int.class || elemType == long.class) density = 7; + if (elemType == long.class || elemType == double.class) { MAX /= 2; MIN /= 2; } + for (int nargs = MIN; nargs <= MAX; nargs = nextArgCount(nargs, density, MAX)) { + Object[] args = makeTestArray(elemType, nargs); + MethodHandle varargsArray = ValueConversions.varargsArray(arrayType, nargs); + MethodType vaType = varargsArray.type(); + assertEquals(arrayType, vaType.returnType()); + if (nargs != 0) { + assertEquals(elemType, vaType.parameterType(0)); + assertEquals(elemType, vaType.parameterType(vaType.parameterCount()-1)); + } + assertEquals(MethodType.methodType(arrayType, Collections.>nCopies(nargs, elemType)), + vaType); + Object res = varargsArray.invokeWithArguments(args); + String resString = toArrayString(res); + assertEquals(Arrays.toString(args), resString); + + MethodHandle spreader = varargsArray.asSpreader(arrayType, nargs); + MethodType stype = spreader.type(); + assert(stype == MethodType.methodType(arrayType, arrayType)); + if (nargs <= 5) { + // invoke target as a spreader also: + Object res2 = spreader.invokeWithArguments((Object)res); + String res2String = toArrayString(res2); + assertEquals(Arrays.toString(args), res2String); + // invoke the spreader on a generic Object[] array; check for error + try { + Object res3 = spreader.invokeWithArguments((Object)args); + String res3String = toArrayString(res3); + assertTrue(arrayType.getName(), arrayType.isAssignableFrom(Object[].class)); + assertEquals(Arrays.toString(args), res3String); + } catch (ClassCastException ex) { + assertFalse(arrayType.getName(), arrayType.isAssignableFrom(Object[].class)); + } + } + if (nargs == 0) { + // invoke spreader on null arglist + Object res3 = spreader.invokeWithArguments((Object)null); + String res3String = toArrayString(res3); + assertEquals(Arrays.toString(args), res3String); + } + } + } + + private static Object[] makeTestArray(Class elemType, int len) { + Wrapper elem = null; + if (elemType.isPrimitive()) + elem = Wrapper.forPrimitiveType(elemType); + else if (Wrapper.isWrapperType(elemType)) + elem = Wrapper.forWrapperType(elemType); + Object[] args = new Object[len]; + for (int i = 0; i < len; i++) { + Object arg = i * 100; + if (elem == null) { + if (elemType == String.class) + arg = "#"+arg; + arg = elemType.cast(arg); // just to make sure + } else { + switch (elem) { + case BOOLEAN: arg = (i % 3 == 0); break; + case CHAR: arg = 'a' + i; break; + case LONG: arg = (long)i * 1000_000_000; break; + case FLOAT: arg = (float)i / 100; break; + case DOUBLE: arg = (double)i / 1000_000; break; + } + arg = elem.cast(arg, elemType); + } + args[i] = arg; + } + //System.out.println(elemType.getName()+Arrays.toString(args)); + return args; + } + + private static String toArrayString(Object a) { + if (a == null) return "null"; + Class elemType = a.getClass().getComponentType(); + if (elemType == null) return a.toString(); + if (elemType.isPrimitive()) { + switch (Wrapper.forPrimitiveType(elemType)) { + case INT: return Arrays.toString((int[])a); + case BYTE: return Arrays.toString((byte[])a); + case BOOLEAN: return Arrays.toString((boolean[])a); + case SHORT: return Arrays.toString((short[])a); + case CHAR: return Arrays.toString((char[])a); + case FLOAT: return Arrays.toString((float[])a); + case LONG: return Arrays.toString((long[])a); + case DOUBLE: return Arrays.toString((double[])a); + } + } + return Arrays.toString((Object[])a); + } + + @Test + public void testVarargsList() throws Throwable { + //System.out.println("varargsList"); + final int MIN = START_ARITY; + final int MAX = MAX_ARITY-2; // 253+1 would cause parameter overflow with 'this' added + for (int nargs = MIN; nargs <= MAX; nargs = nextArgCount(nargs, 7, MAX)) { + MethodHandle target = ValueConversions.varargsList(nargs); + Object[] args = new Object[nargs]; + for (int i = 0; i < nargs; i++) + args[i] = "#"+i; + Object res = target.invokeWithArguments(args); + assertEquals(Arrays.asList(args), res); + } + } +}