Mercurial > hg > shenandoah-preopenjdk-archive > openjdk8 > jdk
changeset 9980:2b3bf1a8ed13
8037209: Improvements and cleanups to bytecode assembly for lambda forms
Reviewed-by: vlivanov, psandoz
Contributed-by: john.r.rose@oracle.com
author | vlivanov |
---|---|
date | Wed, 10 Sep 2014 18:34:02 +0400 |
parents | 0fefdcab3608 |
children | a31f793f509b |
files | src/share/classes/java/lang/invoke/DirectMethodHandle.java src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java src/share/classes/java/lang/invoke/LambdaForm.java src/share/classes/java/lang/invoke/MethodHandleImpl.java src/share/classes/java/lang/invoke/MethodHandleStatics.java src/share/classes/java/lang/invoke/MethodType.java src/share/classes/sun/invoke/util/VerifyType.java src/share/classes/sun/invoke/util/Wrapper.java |
diffstat | 8 files changed, 335 insertions(+), 118 deletions(-) [+] |
line wrap: on
line diff
--- a/src/share/classes/java/lang/invoke/DirectMethodHandle.java Wed Sep 10 18:34:02 2014 +0400 +++ b/src/share/classes/java/lang/invoke/DirectMethodHandle.java Wed Sep 10 18:34:02 2014 +0400 @@ -260,9 +260,10 @@ } else { names[GET_MEMBER] = new Name(Lazy.NF_internalMemberName, names[DMH_THIS]); } + assert(findDirectMethodHandle(names[GET_MEMBER]) == names[DMH_THIS]); Object[] outArgs = Arrays.copyOfRange(names, ARG_BASE, GET_MEMBER+1, Object[].class); assert(outArgs[outArgs.length-1] == names[GET_MEMBER]); // look, shifted args! - int result = LambdaForm.LAST_RESULT; + int result = LAST_RESULT; if (doesAlloc) { assert(outArgs[outArgs.length-2] == names[NEW_OBJ]); // got to move this one System.arraycopy(outArgs, 0, outArgs, 1, outArgs.length-2); @@ -277,6 +278,16 @@ return lform; } + static Object findDirectMethodHandle(Name name) { + if (name.function == Lazy.NF_internalMemberName || + name.function == Lazy.NF_internalMemberNameEnsureInit || + name.function == Lazy.NF_constructorMethod) { + assert(name.arguments.length == 1); + return name.arguments[0]; + } + return null; + } + private static void maybeCompile(LambdaForm lform, MemberName m) { if (VerifyAccess.isSamePackage(m.getDeclaringClass(), MethodHandle.class)) // Help along bootstrapping...
--- a/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Wed Sep 10 18:34:02 2014 +0400 +++ b/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Wed Sep 10 18:34:02 2014 +0400 @@ -25,21 +25,20 @@ package java.lang.invoke; -import sun.invoke.util.VerifyAccess; -import static java.lang.invoke.LambdaForm.*; - -import sun.invoke.util.Wrapper; - import java.io.*; import java.util.*; +import java.lang.reflect.Modifier; import jdk.internal.org.objectweb.asm.*; -import java.lang.reflect.*; +import static java.lang.invoke.LambdaForm.*; +import static java.lang.invoke.LambdaForm.BasicType.*; import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandleNatives.Constants.*; -import static java.lang.invoke.LambdaForm.BasicType.*; +import sun.invoke.util.ValueConversions; +import sun.invoke.util.VerifyAccess; import sun.invoke.util.VerifyType; +import sun.invoke.util.Wrapper; import sun.reflect.misc.ReflectUtil; /** @@ -74,7 +73,11 @@ private final LambdaForm lambdaForm; private final String invokerName; private final MethodType invokerType; - private final int[] localsMap; + + /** Info about local variables in compiled lambda form */ + private final int[] localsMap; // index + private final BasicType[] localTypes; // basic type + private final Class<?>[] localClasses; // type /** ASM bytecode generation. */ private ClassWriter cw; @@ -83,6 +86,7 @@ private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory(); private static final Class<?> HOST_CLASS = LambdaForm.class; + /** Main constructor; other constructors delegate to this one. */ private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize, String className, String invokerName, MethodType invokerType) { if (invokerName.contains(".")) { @@ -98,18 +102,26 @@ this.lambdaForm = lambdaForm; this.invokerName = invokerName; this.invokerType = invokerType; - this.localsMap = new int[localsMapSize]; + this.localsMap = new int[localsMapSize+1]; + // last entry of localsMap is count of allocated local slots + this.localTypes = new BasicType[localsMapSize+1]; + this.localClasses = new Class<?>[localsMapSize+1]; } + /** For generating LambdaForm interpreter entry points. */ private InvokerBytecodeGenerator(String className, String invokerName, MethodType invokerType) { this(null, invokerType.parameterCount(), className, invokerName, invokerType); // Create an array to map name indexes to locals indexes. + localTypes[localTypes.length - 1] = V_TYPE; for (int i = 0; i < localsMap.length; i++) { localsMap[i] = invokerType.parameterSlotCount() - invokerType.parameterSlotDepth(i); + if (i < invokerType.parameterCount()) + localTypes[i] = basicType(invokerType.parameterType(i)); } } + /** For generating customized code for a single LambdaForm. */ private InvokerBytecodeGenerator(String className, LambdaForm form, MethodType invokerType) { this(form, form.names.length, className, form.debugName, invokerType); @@ -117,7 +129,11 @@ Name[] names = form.names; for (int i = 0, index = 0; i < localsMap.length; i++) { localsMap[i] = index; - index += names[i].type.basicTypeSlots(); + if (i < names.length) { + BasicType type = names[i].type(); + index += type.basicTypeSlots(); + localTypes[i] = type; + } } } @@ -148,7 +164,6 @@ static void maybeDump(final String className, final byte[] classFile) { if (DUMP_CLASS_FILES) { - System.out.println("dump: " + className); java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { @@ -156,6 +171,7 @@ String dumpName = className; //dumpName = dumpName.replace('/', '-'); File dumpFile = new File(DUMP_CLASS_FILES_DIR, dumpName+".class"); + System.out.println("dump: " + dumpFile); dumpFile.getParentFile().mkdirs(); FileOutputStream file = new FileOutputStream(dumpFile); file.write(classFile); @@ -204,7 +220,7 @@ String constantPlaceholder(Object arg) { String cpPlaceholder = "CONSTANT_PLACEHOLDER_" + cph++; - if (DUMP_CLASS_FILES) cpPlaceholder += " <<" + arg.toString() + ">>"; // debugging aid + if (DUMP_CLASS_FILES) cpPlaceholder += " <<" + debugString(arg) + ">>"; // debugging aid if (cpPatches.containsKey(cpPlaceholder)) { throw new InternalError("observed CP placeholder twice: " + cpPlaceholder); } @@ -225,6 +241,17 @@ return res; } + private static String debugString(Object arg) { + if (arg instanceof MethodHandle) { + MethodHandle mh = (MethodHandle) arg; + MemberName member = mh.internalMemberName(); + if (member != null) + return member.toString(); + return mh.debugString(); + } + return arg.toString(); + } + /** * Extract the number of constant pool entries from a given class file. * @@ -400,6 +427,30 @@ emitStoreInsn(L_TYPE, index); } + private void freeFrameLocal(int oldFrameLocal) { + int i = indexForFrameLocal(oldFrameLocal); + if (i < 0) return; + BasicType type = localTypes[i]; + int newFrameLocal = makeLocalTemp(type); + mv.visitVarInsn(loadInsnOpcode(type), oldFrameLocal); + mv.visitVarInsn(storeInsnOpcode(type), newFrameLocal); + assert(localsMap[i] == oldFrameLocal); + localsMap[i] = newFrameLocal; + assert(indexForFrameLocal(oldFrameLocal) < 0); + } + private int indexForFrameLocal(int frameLocal) { + for (int i = 0; i < localsMap.length; i++) { + if (localsMap[i] == frameLocal && localTypes[i] != V_TYPE) + return i; + } + return -1; + } + private int makeLocalTemp(BasicType type) { + int frameLocal = localsMap[localsMap.length - 1]; + localsMap[localsMap.length - 1] = frameLocal + type.basicTypeSlots(); + return frameLocal; + } + /** * Emit a boxing call. * @@ -421,41 +472,79 @@ String owner = "java/lang/" + wrapper.wrapperType().getSimpleName(); String name = wrapper.primitiveSimpleName() + "Value"; String desc = "()" + wrapper.basicTypeChar(); - mv.visitTypeInsn(Opcodes.CHECKCAST, owner); + emitReferenceCast(wrapper.wrapperType(), null); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false); } /** - * Emit an implicit conversion. + * Emit an implicit conversion for an argument which must be of the given pclass. + * This is usually a no-op, except when pclass is a subword type or a reference other than Object or an interface. * * @param ptype type of value present on stack * @param pclass type of value required on stack + * @param arg compile-time representation of value on stack (Node, constant) or null if none */ - private void emitImplicitConversion(BasicType ptype, Class<?> pclass) { + private void emitImplicitConversion(BasicType ptype, Class<?> pclass, Object arg) { assert(basicType(pclass) == ptype); // boxing/unboxing handled by caller if (pclass == ptype.basicTypeClass() && ptype != L_TYPE) return; // nothing to do switch (ptype) { - case L_TYPE: - if (VerifyType.isNullConversion(Object.class, pclass)) + case L_TYPE: + if (VerifyType.isNullConversion(Object.class, pclass, false)) { + if (PROFILE_LEVEL > 0) + emitReferenceCast(Object.class, arg); + return; + } + emitReferenceCast(pclass, arg); + return; + case I_TYPE: + if (!VerifyType.isNullConversion(int.class, pclass, false)) + emitPrimCast(ptype.basicTypeWrapper(), Wrapper.forPrimitiveType(pclass)); return; - if (isStaticallyNameable(pclass)) { - mv.visitTypeInsn(Opcodes.CHECKCAST, getInternalName(pclass)); - } else { - mv.visitLdcInsn(constantPlaceholder(pclass)); - mv.visitTypeInsn(Opcodes.CHECKCAST, CLS); - mv.visitInsn(Opcodes.SWAP); - mv.visitMethodInsn(Opcodes.INVOKESTATIC, MHI, "castReference", CLL_SIG, false); - if (pclass.isArray()) - mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY); + } + throw newInternalError("bad implicit conversion: tc="+ptype+": "+pclass); + } + + /** Update localClasses type map. Return true if the information is already present. */ + private boolean assertStaticType(Class<?> cls, Name n) { + int local = n.index(); + Class<?> aclass = localClasses[local]; + if (aclass != null && (aclass == cls || cls.isAssignableFrom(aclass))) { + return true; // type info is already present + } else if (aclass == null || aclass.isAssignableFrom(cls)) { + localClasses[local] = cls; // type info can be improved + } + return false; + } + + private void emitReferenceCast(Class<?> cls, Object arg) { + Name writeBack = null; // local to write back result + if (arg instanceof Name) { + Name n = (Name) arg; + if (assertStaticType(cls, n)) + return; // this cast was already performed + if (lambdaForm.useCount(n) > 1) { + // This guy gets used more than once. + writeBack = n; } - return; - case I_TYPE: - if (!VerifyType.isNullConversion(int.class, pclass)) - emitPrimCast(ptype.basicTypeWrapper(), Wrapper.forPrimitiveType(pclass)); - return; } - throw new InternalError("bad implicit conversion: tc="+ptype+": "+pclass); + if (isStaticallyNameable(cls)) { + String sig = getInternalName(cls); + mv.visitTypeInsn(Opcodes.CHECKCAST, sig); + } else { + mv.visitLdcInsn(constantPlaceholder(cls)); + mv.visitTypeInsn(Opcodes.CHECKCAST, CLS); + mv.visitInsn(Opcodes.SWAP); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, MHI, "castReference", CLL_SIG, false); + if (Object[].class.isAssignableFrom(cls)) + mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY); + else if (PROFILE_LEVEL > 0) + mv.visitTypeInsn(Opcodes.CHECKCAST, OBJ); + } + if (writeBack != null) { + mv.visitInsn(Opcodes.DUP); + emitAstoreInsn(writeBack.index()); + } } /** @@ -477,7 +566,11 @@ } private static String getInternalName(Class<?> c) { - assert(VerifyAccess.isTypeVisible(c, Object.class)); + if (c == Object.class) return OBJ; + else if (c == Object[].class) return OBJARY; + else if (c == Class.class) return CLS; + else if (c == MethodHandle.class) return MH; + assert(VerifyAccess.isTypeVisible(c, Object.class)) : c.getName(); return c.getName().replace('.', '/'); } @@ -506,39 +599,32 @@ // iterate over the form's names, generating bytecode instructions for each // start iterating at the first name following the arguments + Name onStack = null; for (int i = lambdaForm.arity; i < lambdaForm.names.length; i++) { Name name = lambdaForm.names[i]; MemberName member = name.function.member(); + Class<?> rtype = name.function.methodType().returnType(); + + emitStoreResult(onStack); + onStack = name; // unless otherwise modified below if (isSelectAlternative(i)) { - emitSelectAlternative(name, lambdaForm.names[i + 1]); + onStack = emitSelectAlternative(name, lambdaForm.names[i + 1]); i++; // skip MH.invokeBasic of the selectAlternative result } else if (isGuardWithCatch(i)) { - emitGuardWithCatch(i); + onStack = emitGuardWithCatch(i); i = i+2; // Jump to the end of GWC idiom + } else if (isNewArray(rtype, name)) { + emitNewArray(rtype, name); } else if (isStaticallyInvocable(member)) { - emitStaticInvoke(member, name); + emitStaticInvoke(name); } else { emitInvoke(name); } - - // Update cached form name's info in case an intrinsic spanning multiple names was encountered. - name = lambdaForm.names[i]; - member = name.function.member(); - - // store the result from evaluating to the target name in a local if required - // (if this is the last value, i.e., the one that is going to be returned, - // avoid store/load/return and just return) - if (i == lambdaForm.names.length - 1 && i == lambdaForm.result) { - // return value - do nothing - } else if (name.type != V_TYPE) { - // non-void: actually assign - emitStoreInsn(name.type, name.index()); - } } // return statement - emitReturn(); + emitReturn(onStack); classFileEpilogue(); bogusMethod(lambdaForm); @@ -552,25 +638,24 @@ * Emit an invoke for the given name. */ void emitInvoke(Name name) { + assert(!isLinkerMethodInvoke(name)); // should use the static path for these if (true) { // push receiver MethodHandle target = name.function.resolvedHandle; assert(target != null) : name.exprString(); mv.visitLdcInsn(constantPlaceholder(target)); - mv.visitTypeInsn(Opcodes.CHECKCAST, MH); + emitReferenceCast(MethodHandle.class, target); } else { // load receiver emitAloadInsn(0); - mv.visitTypeInsn(Opcodes.CHECKCAST, MH); + emitReferenceCast(MethodHandle.class, null); mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", LF_SIG); mv.visitFieldInsn(Opcodes.GETFIELD, LF, "names", LFN_SIG); // TODO more to come } // push arguments - for (int i = 0; i < name.arguments.length; i++) { - emitPushArgument(name, i); - } + emitPushArguments(name); // invocation MethodType type = name.function.methodType(); @@ -585,6 +670,10 @@ //MethodHandle.class already covered }; + static boolean isStaticallyInvocable(Name name) { + return isStaticallyInvocable(name.function.member()); + } + static boolean isStaticallyInvocable(MemberName member) { if (member == null) return false; if (member.isConstructor()) return false; @@ -611,6 +700,8 @@ } static boolean isStaticallyNameable(Class<?> cls) { + if (cls == Object.class) + return true; while (cls.isArray()) cls = cls.getComponentType(); if (cls.isPrimitive()) @@ -631,12 +722,17 @@ return false; } + void emitStaticInvoke(Name name) { + emitStaticInvoke(name.function.member(), name); + } + /** * Emit an invoke for the given name, using the MemberName directly. */ void emitStaticInvoke(MemberName member, Name name) { assert(member.equals(name.function.member())); - String cname = getInternalName(member.getDeclaringClass()); + Class<?> defc = member.getDeclaringClass(); + String cname = getInternalName(defc); String mname = member.getName(); String mtype; byte refKind = member.getReferenceKind(); @@ -653,9 +749,7 @@ } // push arguments - for (int i = 0; i < name.arguments.length; i++) { - emitPushArgument(name, i); - } + emitPushArguments(name); // invocation if (member.isMethod()) { @@ -666,6 +760,54 @@ mtype = MethodType.toFieldDescriptorString(member.getFieldType()); mv.visitFieldInsn(refKindOpcode(refKind), cname, mname, mtype); } + // Issue a type assertion for the result, so we can avoid casts later. + if (name.type == L_TYPE) { + Class<?> rtype = member.getInvocationType().returnType(); + assert(!rtype.isPrimitive()); + if (rtype != Object.class && !rtype.isInterface()) { + assertStaticType(rtype, name); + } + } + } + + boolean isNewArray(Class<?> rtype, Name name) { + return rtype.isArray() && + isStaticallyNameable(rtype) && + isArrayBuilder(name.function.resolvedHandle) && + name.arguments.length > 0; + } + + void emitNewArray(Class<?> rtype, Name name) throws InternalError { + Class<?> arrayElementType = rtype.getComponentType(); + emitIconstInsn(name.arguments.length); + int xas; + if (!arrayElementType.isPrimitive()) { + mv.visitTypeInsn(Opcodes.ANEWARRAY, getInternalName(arrayElementType)); + xas = Opcodes.AASTORE; + } else { + int tc; + switch (Wrapper.forPrimitiveType(arrayElementType)) { + case BOOLEAN: tc = Opcodes.T_BOOLEAN; xas = Opcodes.BASTORE; break; + case BYTE: tc = Opcodes.T_BYTE; xas = Opcodes.BASTORE; break; + case CHAR: tc = Opcodes.T_CHAR; xas = Opcodes.CASTORE; break; + case SHORT: tc = Opcodes.T_SHORT; xas = Opcodes.SASTORE; break; + case INT: tc = Opcodes.T_INT; xas = Opcodes.IASTORE; break; + case LONG: tc = Opcodes.T_LONG; xas = Opcodes.LASTORE; break; + case FLOAT: tc = Opcodes.T_FLOAT; xas = Opcodes.FASTORE; break; + case DOUBLE: tc = Opcodes.T_DOUBLE; xas = Opcodes.DASTORE; break; + default: throw new InternalError(rtype.getName()); + } + mv.visitIntInsn(Opcodes.NEWARRAY, tc); + } + // store arguments + for (int i = 0; i < name.arguments.length; i++) { + mv.visitInsn(Opcodes.DUP); + emitIconstInsn(i); + emitPushArgument(name, i); + mv.visitInsn(xas); + } + // the array is left on the stack + assertStaticType(rtype, name); } int refKindOpcode(byte refKind) { switch (refKind) { @@ -681,6 +823,24 @@ throw new InternalError("refKind="+refKind); } + static boolean isArrayBuilder(MethodHandle fn) { + if (fn == null) + return false; + MethodType mtype = fn.type(); + Class<?> rtype = mtype.returnType(); + Class<?> arrayElementType = rtype.getComponentType(); + if (arrayElementType == null) + return false; + List<Class<?>> ptypes = mtype.parameterList(); + int size = ptypes.size(); + if (!ptypes.equals(Collections.nCopies(size, arrayElementType))) + return false; + // Assume varargsArray caches pointers. + if (fn != ValueConversions.varargsArray(rtype, size)) + return false; + return true; + } + /** * Check if MemberName is a call to a method named {@code name} in class {@code declaredClass}. */ @@ -708,6 +868,21 @@ } /** + * Check if MemberName is a call to MethodHandle.linkToStatic, etc. + */ + private boolean isLinkerMethodInvoke(Name name) { + if (name.function == null) + return false; + if (name.arguments.length < 1) + return false; // must have MH argument + MemberName member = name.function.member(); + return member != null && + member.getDeclaringClass() == MethodHandle.class && + !member.isPublic() && member.isStatic() && + member.getName().startsWith("linkTo"); + } + + /** * Check if i-th name is a call to MethodHandleImpl.selectAlternative. */ private boolean isSelectAlternative(int pos) { @@ -755,7 +930,9 @@ * t4:I=MethodHandle.invokeBasic(t3:L,a1:I);t4:I} * }</pre></blockquote> */ - private void emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) { + private Name emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) { + assert isStaticallyInvocable(invokeBasicName); + Name receiver = (Name) invokeBasicName.arguments[0]; Label L_fallback = new Label(); @@ -763,15 +940,15 @@ // load test result emitPushArgument(selectAlternativeName, 0); - mv.visitInsn(Opcodes.ICONST_1); // if_icmpne L_fallback - mv.visitJumpInsn(Opcodes.IF_ICMPNE, L_fallback); + mv.visitJumpInsn(Opcodes.IFEQ, L_fallback); // invoke selectAlternativeName.arguments[1] + Class<?>[] preForkClasses = localClasses.clone(); emitPushArgument(selectAlternativeName, 1); // get 2nd argument of selectAlternative emitAstoreInsn(receiver.index()); // store the MH in the receiver slot - emitInvoke(invokeBasicName); + emitStaticInvoke(invokeBasicName); // goto L_done mv.visitJumpInsn(Opcodes.GOTO, L_done); @@ -780,12 +957,17 @@ mv.visitLabel(L_fallback); // invoke selectAlternativeName.arguments[2] + System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length); emitPushArgument(selectAlternativeName, 2); // get 3rd argument of selectAlternative emitAstoreInsn(receiver.index()); // store the MH in the receiver slot - emitInvoke(invokeBasicName); + emitStaticInvoke(invokeBasicName); // L_done: mv.visitLabel(L_done); + // for now do not bother to merge typestate; just reset to the dominator state + System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length); + + return invokeBasicName; // return what's on stack } /** @@ -808,7 +990,7 @@ * return a3.invokeBasic(ex, a6, a7); * }} */ - private void emitGuardWithCatch(int pos) { + private Name emitGuardWithCatch(int pos) { Name args = lambdaForm.names[pos]; Name invoker = lambdaForm.names[pos+1]; Name result = lambdaForm.names[pos+2]; @@ -859,6 +1041,12 @@ mv.visitInsn(Opcodes.ATHROW); mv.visitLabel(L_done); + + return result; + } + + private void emitPushArguments(Name args) { + emitPushArguments(args, 0); } private void emitPushArguments(Name args, int start) { @@ -878,7 +1066,7 @@ if (arg instanceof Name) { Name n = (Name) arg; emitLoadInsn(n.type, n.index()); - emitImplicitConversion(n.type, ptype); + emitImplicitConversion(n.type, ptype, n); } else if ((arg == null || arg instanceof String) && bptype == L_TYPE) { emitConst(arg); } else { @@ -886,15 +1074,25 @@ emitConst(arg); } else { mv.visitLdcInsn(constantPlaceholder(arg)); - emitImplicitConversion(L_TYPE, ptype); + emitImplicitConversion(L_TYPE, ptype, arg); } } } /** + * Store the name to its local, if necessary. + */ + private void emitStoreResult(Name name) { + if (name != null && name.type != V_TYPE) { + // non-void: actually assign + emitStoreInsn(name.type, name.index()); + } + } + + /** * Emits a return statement from a LF invoker. If required, the result type is cast to the correct return type. */ - private void emitReturn() { + private void emitReturn(Name onStack) { // return statement Class<?> rclass = invokerType.returnType(); BasicType rtype = lambdaForm.returnType(); @@ -907,12 +1105,11 @@ LambdaForm.Name rn = lambdaForm.names[lambdaForm.result]; // put return value on the stack if it is not already there - if (lambdaForm.result != lambdaForm.names.length - 1 || - lambdaForm.result < lambdaForm.arity) { - emitLoadInsn(rn.type, lambdaForm.result); + if (rn != onStack) { + emitLoadInsn(rtype, lambdaForm.result); } - emitImplicitConversion(rtype, rclass); + emitImplicitConversion(rtype, rclass, rn); // generate actual return statement emitReturnInsn(rtype);
--- a/src/share/classes/java/lang/invoke/LambdaForm.java Wed Sep 10 18:34:02 2014 +0400 +++ b/src/share/classes/java/lang/invoke/LambdaForm.java Wed Sep 10 18:34:02 2014 +0400 @@ -464,7 +464,7 @@ return sig.indexOf('_'); } static BasicType signatureReturn(String sig) { - return basicType(sig.charAt(signatureArity(sig)+1)); + return basicType(sig.charAt(signatureArity(sig) + 1)); } static boolean isValidSignature(String sig) { int arity = sig.indexOf('_'); @@ -582,7 +582,7 @@ isCompiled = true; return vmentry; } catch (Error | Exception ex) { - throw newInternalError("compileToBytecode", ex); + throw newInternalError("compileToBytecode: " + this, ex); } }
--- a/src/share/classes/java/lang/invoke/MethodHandleImpl.java Wed Sep 10 18:34:02 2014 +0400 +++ b/src/share/classes/java/lang/invoke/MethodHandleImpl.java Wed Sep 10 18:34:02 2014 +0400 @@ -109,7 +109,7 @@ } static String name(Class<?> arrayClass, boolean isSetter) { Class<?> elemClass = arrayClass.getComponentType(); - if (elemClass == null) throw new IllegalArgumentException(); + if (elemClass == null) throw newIllegalArgumentException("not an array", arrayClass); return (!isSetter ? "getElement" : "setElement") + Wrapper.basicTypeChar(elemClass); } static final boolean USE_WEAKLY_TYPED_ARRAY_ACCESSORS = false; // FIXME: decide @@ -179,13 +179,17 @@ for (int i = 0; i <= INARG_COUNT; i++) { Class<?> src = (i == INARG_COUNT) ? dstType.returnType() : srcType.parameterType(i); Class<?> dst = (i == INARG_COUNT) ? srcType.returnType() : dstType.parameterType(i); - if (!VerifyType.isNullConversion(src, dst) || + if (!VerifyType.isNullConversion(src, dst, false) || level <= 1 && dst.isInterface() && !dst.isAssignableFrom(src)) { needConv[i] = true; conversions++; } } boolean retConv = needConv[INARG_COUNT]; + if (retConv && srcType.returnType() == void.class) { + retConv = false; + conversions--; + } final int IN_MH = 0; final int INARG_BASE = 1; @@ -193,6 +197,7 @@ final int NAME_LIMIT = INARG_LIMIT + conversions + 1; final int RETURN_CONV = (!retConv ? -1 : NAME_LIMIT - 1); final int OUT_CALL = (!retConv ? NAME_LIMIT : RETURN_CONV) - 1; + final int RESULT = (srcType.returnType() == void.class ? -1 : NAME_LIMIT - 1); // Now build a LambdaForm. MethodType lambdaType = srcType.basicType().invokerType(); @@ -230,7 +235,7 @@ if (dst.isPrimitive()) { // Caller has boxed a primitive. Unbox it for the target. Wrapper w = Wrapper.forPrimitiveType(dst); - if (level == 0 || VerifyType.isNullConversion(src, w.wrapperType())) { + if (level == 0 || VerifyType.isNullConversion(src, w.wrapperType(), false)) { fn = ValueConversions.unbox(dst); } else if (src == Object.class || !Wrapper.isWrapperType(src)) { // Examples: Object->int, Number->int, Comparable->int; Byte->int, Character->int @@ -289,7 +294,7 @@ assert(RETURN_CONV == names.length-1); } - LambdaForm form = new LambdaForm("convert", lambdaType.parameterCount(), names); + LambdaForm form = new LambdaForm("convert", lambdaType.parameterCount(), names, RESULT); return SimpleMethodHandle.make(srcType, form); }
--- a/src/share/classes/java/lang/invoke/MethodHandleStatics.java Wed Sep 10 18:34:02 2014 +0400 +++ b/src/share/classes/java/lang/invoke/MethodHandleStatics.java Wed Sep 10 18:34:02 2014 +0400 @@ -46,8 +46,10 @@ static final boolean TRACE_INTERPRETER; static final boolean TRACE_METHOD_LINKAGE; static final Integer COMPILE_THRESHOLD; + static final int PROFILE_LEVEL; + static { - final Object[] values = { false, false, false, false, null }; + final Object[] values = { false, false, false, false, null, null }; AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { values[0] = Boolean.getBoolean("java.lang.invoke.MethodHandle.DEBUG_NAMES"); @@ -55,6 +57,7 @@ values[2] = Boolean.getBoolean("java.lang.invoke.MethodHandle.TRACE_INTERPRETER"); values[3] = Boolean.getBoolean("java.lang.invoke.MethodHandle.TRACE_METHOD_LINKAGE"); values[4] = Integer.getInteger("java.lang.invoke.MethodHandle.COMPILE_THRESHOLD"); + values[5] = Integer.getInteger("java.lang.invoke.MethodHandle.PROFILE_LEVEL", 0); return null; } }); @@ -63,6 +66,7 @@ TRACE_INTERPRETER = (Boolean) values[2]; TRACE_METHOD_LINKAGE = (Boolean) values[3]; COMPILE_THRESHOLD = (Integer) values[4]; + PROFILE_LEVEL = (Integer) values[5]; } /** Tell if any of the debugging switches are turned on.
--- a/src/share/classes/java/lang/invoke/MethodType.java Wed Sep 10 18:34:02 2014 +0400 +++ b/src/share/classes/java/lang/invoke/MethodType.java Wed Sep 10 18:34:02 2014 +0400 @@ -731,13 +731,13 @@ /*non-public*/ boolean isViewableAs(MethodType newType) { - if (!VerifyType.isNullConversion(returnType(), newType.returnType())) + if (!VerifyType.isNullConversion(returnType(), newType.returnType(), true)) return false; int argc = parameterCount(); if (argc != newType.parameterCount()) return false; for (int i = 0; i < argc; i++) { - if (!VerifyType.isNullConversion(newType.parameterType(i), parameterType(i))) + if (!VerifyType.isNullConversion(newType.parameterType(i), parameterType(i), true)) return false; } return true;
--- a/src/share/classes/sun/invoke/util/VerifyType.java Wed Sep 10 18:34:02 2014 +0400 +++ b/src/share/classes/sun/invoke/util/VerifyType.java Wed Sep 10 18:34:02 2014 +0400 @@ -40,18 +40,38 @@ /** * True if a value can be stacked as the source type and unstacked as the * destination type, without violating the JVM's type consistency. + * <p> + * If both types are references, we apply the verifier's subclass check + * (or subtyping, if keepInterfaces). + * If the src type is a type guaranteed to be null (Void) it can be converted + * to any other reference type. + * <p> + * If both types are primitives, we apply the verifier's primitive conversions. + * These do not include Java conversions such as long to double, since those + * require computation and (in general) stack depth changes. + * But very simple 32-bit viewing changes, such as byte to int, + * are null conversions, because they do not require any computation. + * These conversions are from any type to a wider type up to 32 bits, + * as long as the conversion is not signed to unsigned (byte to char). + * <p> + * The primitive type 'void' does not interconvert with any other type, + * even though it is legal to drop any type from the stack and "return void". + * The stack effects, though are different between void and any other type, + * so it is safer to report a non-trivial conversion. * * @param src the type of a stacked value * @param dst the type by which we'd like to treat it + * @param keepInterfaces if false, we treat any interface as if it were Object * @return whether the retyping can be done without motion or reformatting */ - public static boolean isNullConversion(Class<?> src, Class<?> dst) { + public static boolean isNullConversion(Class<?> src, Class<?> dst, boolean keepInterfaces) { if (src == dst) return true; // Verifier allows any interface to be treated as Object: - if (dst.isInterface()) dst = Object.class; - if (src.isInterface()) src = Object.class; - if (src == dst) return true; // check again - if (dst == void.class) return true; // drop any return value + if (!keepInterfaces) { + if (dst.isInterface()) dst = Object.class; + if (src.isInterface()) src = Object.class; + if (src == dst) return true; // check again + } if (isNullType(src)) return !dst.isPrimitive(); if (!src.isPrimitive()) return dst.isAssignableFrom(src); if (!dst.isPrimitive()) return false; @@ -82,25 +102,13 @@ * Is the given type java.lang.Null or an equivalent null-only type? */ public static boolean isNullType(Class<?> type) { - if (type == null) return false; - return type == NULL_CLASS - // This one may also be used as a null type. - // TO DO: Decide if we really want to legitimize it here. - // Probably we do, unless java.lang.Null really makes it into Java 7 - //|| type == Void.class - // Locally known null-only class: - || type == Empty.class - ; - } - private static final Class<?> NULL_CLASS; - static { - Class<?> nullClass = null; - try { - nullClass = Class.forName("java.lang.Null"); - } catch (ClassNotFoundException ex) { - // OK, we'll cope - } - NULL_CLASS = nullClass; + // Any reference statically typed as Void is guaranteed to be null. + // Therefore, it can be safely treated as a value of any + // other type that admits null, i.e., a reference type. + if (type == Void.class) return true; + // Locally known null-only class: + if (type == Empty.class) return true; + return false; } /** @@ -111,14 +119,14 @@ * @param recv the type of the method handle receiving the call * @return whether the retyping can be done without motion or reformatting */ - public static boolean isNullConversion(MethodType call, MethodType recv) { + public static boolean isNullConversion(MethodType call, MethodType recv, boolean keepInterfaces) { if (call == recv) return true; int len = call.parameterCount(); if (len != recv.parameterCount()) return false; for (int i = 0; i < len; i++) - if (!isNullConversion(call.parameterType(i), recv.parameterType(i))) + if (!isNullConversion(call.parameterType(i), recv.parameterType(i), keepInterfaces)) return false; - return isNullConversion(recv.returnType(), call.returnType()); + return isNullConversion(recv.returnType(), call.returnType(), keepInterfaces); } /**
--- a/src/share/classes/sun/invoke/util/Wrapper.java Wed Sep 10 18:34:02 2014 +0400 +++ b/src/share/classes/sun/invoke/util/Wrapper.java Wed Sep 10 18:34:02 2014 +0400 @@ -230,14 +230,6 @@ */ public <T> T zero(Class<T> type) { return convert(zero, type); } -// /** Produce a wrapper for the given wrapper or primitive type. */ -// public static Wrapper valueOf(Class<?> type) { -// if (isPrimitiveType(type)) -// return forPrimitiveType(type); -// else -// return forWrapperType(type); -// } - /** Return the wrapper that wraps values of the given type. * The type may be {@code Object}, meaning the {@code OBJECT} wrapper. * Otherwise, the type must be a primitive.