--- old/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java 2014-03-24 20:12:11.000000000 +0400 +++ new/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java 2014-03-24 20:12:10.000000000 +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.VerifyType; +import sun.invoke.util.VerifyAccess; +import sun.invoke.util.Wrapper; /** * Code generation backend for LambdaForm. @@ -73,7 +72,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; @@ -82,6 +85,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(".")) { @@ -97,18 +101,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); @@ -116,7 +128,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; + } } } @@ -147,7 +163,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() { public Void run() { @@ -155,6 +170,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); @@ -203,7 +219,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); } @@ -224,6 +240,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. * @@ -402,6 +429,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. * @@ -423,41 +474,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; - 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); + case I_TYPE: + if (!VerifyType.isNullConversion(int.class, pclass, false)) + emitPrimCast(ptype.basicTypeWrapper(), Wrapper.forPrimitiveType(pclass)); + return; + } + 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()); + } } /** @@ -479,6 +568,10 @@ } private static String getInternalName(Class c) { + 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)); return c.getName().replace('.', '/'); } @@ -511,6 +604,7 @@ 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(); if (isSelectAlternative(i)) { emitSelectAlternative(name, lambdaForm.names[i + 1]); @@ -518,7 +612,11 @@ } else if (isGuardWithCatch(i)) { emitGuardWithCatch(i); i = i+2; // Jump to the end of GWC idiom + } else if (isNewArray(rtype, name)) { + emitNewArray(rtype, name); } else if (isStaticallyInvocable(member)) { + if (isLinkerMethodInvoke(name)) + preserveLinkerMethodTarget(name); emitStaticInvoke(member, name); } else { emitInvoke(name); @@ -527,6 +625,7 @@ // Update cached form name's info in case an intrinsic spanning multiple names was encountered. name = lambdaForm.names[i]; member = name.function.member(); + rtype = name.function.methodType().returnType(); // 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, @@ -550,20 +649,48 @@ return classFile; } + /** Certain parts of the JVM expect that, when a call to linkToStatic is made, + * the DMH that produced its MN argument is in local #0. + * This is often true, since the lambda form of a DMH will start with + * the DMH 'l'in arg 0 (local #0) and will end with a call to its linker method. + * But if the DMH is transformed, a new lambda form may be created with + * the DMH placed elsewhere. + */ + private void preserveLinkerMethodTarget(Name name) { + Object lastArg = name.arguments[name.arguments.length - 1]; + Name targetMember = (Name) lastArg; + assert(targetMember.type() == L_TYPE && + !targetMember.isParam() && + isStaticallyInvocable(targetMember.function.member()) && + targetMember.arguments.length == 1); + Object targetDMH = DirectMethodHandle.findDirectMethodHandle(targetMember); + assert(targetDMH != null); + if (!(targetDMH instanceof Name && ((Name)targetDMH).index() == 0)) { + // Oops, the DMH is not in a local #0, or is a constant. + int DMH_FRAME_INDEX = 0; // hardwired to local #0 + BasicType DMH_TYPE = L_TYPE; + freeFrameLocal(DMH_FRAME_INDEX); + // Save it to local #0: + emitPushArgument(Object.class, targetDMH); + mv.visitVarInsn(storeInsnOpcode(DMH_TYPE), DMH_FRAME_INDEX); + } + } + /** * 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 @@ -611,6 +738,8 @@ } static boolean isStaticallyNameable(Class cls) { + if (cls == Object.class) + return true; while (cls.isArray()) cls = cls.getComponentType(); if (cls.isPrimitive()) @@ -634,7 +763,8 @@ */ 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(); @@ -664,6 +794,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) { @@ -679,6 +857,29 @@ 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> 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; + } + + static boolean match(MemberName member, MethodHandle fn) { + if (member == null || fn == null) return false; + return member.equals(fn.internalMemberName()); + } + /** * Check if MemberName is a call to a method named {@code name} in class {@code declaredClass}. */ @@ -706,6 +907,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) { @@ -761,12 +977,12 @@ // 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); @@ -778,12 +994,15 @@ 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); // 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); } /** @@ -876,7 +1095,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 { @@ -884,7 +1103,7 @@ emitConst(arg); } else { mv.visitLdcInsn(constantPlaceholder(arg)); - emitImplicitConversion(L_TYPE, ptype); + emitImplicitConversion(L_TYPE, ptype, arg); } } } @@ -907,10 +1126,10 @@ // 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); + emitLoadInsn(rtype, lambdaForm.result); } - emitImplicitConversion(rtype, rclass); + emitImplicitConversion(rtype, rclass, rn); // generate actual return statement emitReturnInsn(rtype);