diff -r 000000000000 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/InvokerBytecodeGenerator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/InvokerBytecodeGenerator.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,1052 @@ +/* + * Copyright (c) 2012, 2013, 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. + */ + +package java.lang.invoke; + +import sun.invoke.util.VerifyAccess; +import java.lang.invoke.LambdaForm.Name; +import java.lang.invoke.MethodHandles.Lookup; + +import sun.invoke.util.Wrapper; + +import java.io.*; +import java.util.*; + +import jdk.internal.org.objectweb.asm.*; + +import java.lang.reflect.*; +import static java.lang.invoke.MethodHandleStatics.*; +import static java.lang.invoke.MethodHandleNatives.Constants.*; +import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; +import sun.invoke.util.ValueConversions; +import sun.invoke.util.VerifyType; + +/** + * Code generation backend for LambdaForm. + *

+ * @author John Rose, JSR 292 EG + */ +class InvokerBytecodeGenerator { + /** Define class names for convenience. */ + private static final String MH = "java/lang/invoke/MethodHandle"; + private static final String BMH = "java/lang/invoke/BoundMethodHandle"; + private static final String LF = "java/lang/invoke/LambdaForm"; + private static final String LFN = "java/lang/invoke/LambdaForm$Name"; + private static final String CLS = "java/lang/Class"; + private static final String OBJ = "java/lang/Object"; + private static final String OBJARY = "[Ljava/lang/Object;"; + + private static final String LF_SIG = "L" + LF + ";"; + private static final String LFN_SIG = "L" + LFN + ";"; + private static final String LL_SIG = "(L" + OBJ + ";)L" + OBJ + ";"; + + /** Name of its super class*/ + private static final String superName = LF; + + /** Name of new class */ + private final String className; + + /** Name of the source file (for stack trace printing). */ + private final String sourceFile; + + private final LambdaForm lambdaForm; + private final String invokerName; + private final MethodType invokerType; + private final int[] localsMap; + + /** ASM bytecode generation. */ + private ClassWriter cw; + private MethodVisitor mv; + + private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory(); + private static final Class HOST_CLASS = LambdaForm.class; + + private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize, + String className, String invokerName, MethodType invokerType) { + if (invokerName.contains(".")) { + int p = invokerName.indexOf("."); + className = invokerName.substring(0, p); + invokerName = invokerName.substring(p+1); + } + if (DUMP_CLASS_FILES) { + className = makeDumpableClassName(className); + } + this.className = superName + "$" + className; + this.sourceFile = "LambdaForm$" + className; + this.lambdaForm = lambdaForm; + this.invokerName = invokerName; + this.invokerType = invokerType; + this.localsMap = new int[localsMapSize]; + } + + 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. + for (int i = 0; i < localsMap.length; i++) { + localsMap[i] = invokerType.parameterSlotCount() - invokerType.parameterSlotDepth(i); + } + } + + private InvokerBytecodeGenerator(String className, LambdaForm form, MethodType invokerType) { + this(form, form.names.length, + className, form.debugName, invokerType); + // Create an array to map name indexes to locals indexes. + Name[] names = form.names; + for (int i = 0, index = 0; i < localsMap.length; i++) { + localsMap[i] = index; + index += Wrapper.forBasicType(names[i].type).stackSlots(); + } + } + + + /** instance counters for dumped classes */ + private final static HashMap DUMP_CLASS_FILES_COUNTERS; + /** debugging flag for saving generated class files */ + private final static File DUMP_CLASS_FILES_DIR; + + static { + if (DUMP_CLASS_FILES) { + DUMP_CLASS_FILES_COUNTERS = new HashMap<>(); + try { + File dumpDir = new File("DUMP_CLASS_FILES"); + if (!dumpDir.exists()) { + dumpDir.mkdirs(); + } + DUMP_CLASS_FILES_DIR = dumpDir; + System.out.println("Dumping class files to "+DUMP_CLASS_FILES_DIR+"/..."); + } catch (Exception e) { + throw newInternalError(e); + } + } else { + DUMP_CLASS_FILES_COUNTERS = null; + DUMP_CLASS_FILES_DIR = null; + } + } + + 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() { + try { + String dumpName = className; + //dumpName = dumpName.replace('/', '-'); + File dumpFile = new File(DUMP_CLASS_FILES_DIR, dumpName+".class"); + dumpFile.getParentFile().mkdirs(); + FileOutputStream file = new FileOutputStream(dumpFile); + file.write(classFile); + file.close(); + return null; + } catch (IOException ex) { + throw newInternalError(ex); + } + } + }); + } + + } + + private static String makeDumpableClassName(String className) { + Integer ctr; + synchronized (DUMP_CLASS_FILES_COUNTERS) { + ctr = DUMP_CLASS_FILES_COUNTERS.get(className); + if (ctr == null) ctr = 0; + DUMP_CLASS_FILES_COUNTERS.put(className, ctr+1); + } + String sfx = ctr.toString(); + while (sfx.length() < 3) + sfx = "0"+sfx; + className += sfx; + return className; + } + + class CpPatch { + final int index; + final String placeholder; + final Object value; + CpPatch(int index, String placeholder, Object value) { + this.index = index; + this.placeholder = placeholder; + this.value = value; + } + public String toString() { + return "CpPatch/index="+index+",placeholder="+placeholder+",value="+value; + } + } + + Map cpPatches = new HashMap<>(); + + int cph = 0; // for counting constant placeholders + + String constantPlaceholder(Object arg) { + String cpPlaceholder = "CONSTANT_PLACEHOLDER_" + cph++; + if (DUMP_CLASS_FILES) cpPlaceholder += " <<" + arg.toString() + ">>"; // debugging aid + if (cpPatches.containsKey(cpPlaceholder)) { + throw new InternalError("observed CP placeholder twice: " + cpPlaceholder); + } + // insert placeholder in CP and remember the patch + int index = cw.newConst((Object) cpPlaceholder); // TODO check if aready in the constant pool + cpPatches.put(cpPlaceholder, new CpPatch(index, cpPlaceholder, arg)); + return cpPlaceholder; + } + + Object[] cpPatches(byte[] classFile) { + int size = getConstantPoolSize(classFile); + Object[] res = new Object[size]; + for (CpPatch p : cpPatches.values()) { + if (p.index >= size) + throw new InternalError("in cpool["+size+"]: "+p+"\n"+Arrays.toString(Arrays.copyOf(classFile, 20))); + res[p.index] = p.value; + } + return res; + } + + /** + * Extract the number of constant pool entries from a given class file. + * + * @param classFile the bytes of the class file in question. + * @return the number of entries in the constant pool. + */ + private static int getConstantPoolSize(byte[] classFile) { + // The first few bytes: + // u4 magic; + // u2 minor_version; + // u2 major_version; + // u2 constant_pool_count; + return ((classFile[8] & 0xFF) << 8) | (classFile[9] & 0xFF); + } + + /** + * Extract the MemberName of a newly-defined method. + */ + private MemberName loadMethod(byte[] classFile) { + Class invokerClass = loadAndInitializeInvokerClass(classFile, cpPatches(classFile)); + return resolveInvokerMember(invokerClass, invokerName, invokerType); + } + + /** + * Define a given class as anonymous class in the runtime system. + */ + private static Class loadAndInitializeInvokerClass(byte[] classBytes, Object[] patches) { + Class invokerClass = UNSAFE.defineAnonymousClass(HOST_CLASS, classBytes, patches); + UNSAFE.ensureClassInitialized(invokerClass); // Make sure the class is initialized; VM might complain. + return invokerClass; + } + + private static MemberName resolveInvokerMember(Class invokerClass, String name, MethodType type) { + MemberName member = new MemberName(invokerClass, name, type, REF_invokeStatic); + //System.out.println("resolveInvokerMember => "+member); + //for (Method m : invokerClass.getDeclaredMethods()) System.out.println(" "+m); + try { + member = MEMBERNAME_FACTORY.resolveOrFail(REF_invokeStatic, member, HOST_CLASS, ReflectiveOperationException.class); + } catch (ReflectiveOperationException e) { + throw newInternalError(e); + } + //System.out.println("resolveInvokerMember => "+member); + return member; + } + + /** + * Set up class file generation. + */ + private void classFilePrologue() { + cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); + cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, className, null, superName, null); + cw.visitSource(sourceFile, null); + + String invokerDesc = invokerType.toMethodDescriptorString(); + mv = cw.visitMethod(Opcodes.ACC_STATIC, invokerName, invokerDesc, null, null); + } + + /** + * Tear down class file generation. + */ + private void classFileEpilogue() { + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + /* + * Low-level emit helpers. + */ + private void emitConst(Object con) { + if (con == null) { + mv.visitInsn(Opcodes.ACONST_NULL); + return; + } + if (con instanceof Integer) { + emitIconstInsn((int) con); + return; + } + if (con instanceof Long) { + long x = (long) con; + if (x == (short) x) { + emitIconstInsn((int) x); + mv.visitInsn(Opcodes.I2L); + return; + } + } + if (con instanceof Float) { + float x = (float) con; + if (x == (short) x) { + emitIconstInsn((int) x); + mv.visitInsn(Opcodes.I2F); + return; + } + } + if (con instanceof Double) { + double x = (double) con; + if (x == (short) x) { + emitIconstInsn((int) x); + mv.visitInsn(Opcodes.I2D); + return; + } + } + if (con instanceof Boolean) { + emitIconstInsn((boolean) con ? 1 : 0); + return; + } + // fall through: + mv.visitLdcInsn(con); + } + + private void emitIconstInsn(int i) { + int opcode; + switch (i) { + case 0: opcode = Opcodes.ICONST_0; break; + case 1: opcode = Opcodes.ICONST_1; break; + case 2: opcode = Opcodes.ICONST_2; break; + case 3: opcode = Opcodes.ICONST_3; break; + case 4: opcode = Opcodes.ICONST_4; break; + case 5: opcode = Opcodes.ICONST_5; break; + default: + if (i == (byte) i) { + mv.visitIntInsn(Opcodes.BIPUSH, i & 0xFF); + } else if (i == (short) i) { + mv.visitIntInsn(Opcodes.SIPUSH, (char) i); + } else { + mv.visitLdcInsn(i); + } + return; + } + mv.visitInsn(opcode); + } + + /* + * NOTE: These load/store methods use the localsMap to find the correct index! + */ + private void emitLoadInsn(char type, int index) { + int opcode; + switch (type) { + case 'I': opcode = Opcodes.ILOAD; break; + case 'J': opcode = Opcodes.LLOAD; break; + case 'F': opcode = Opcodes.FLOAD; break; + case 'D': opcode = Opcodes.DLOAD; break; + case 'L': opcode = Opcodes.ALOAD; break; + default: + throw new InternalError("unknown type: " + type); + } + mv.visitVarInsn(opcode, localsMap[index]); + } + private void emitAloadInsn(int index) { + emitLoadInsn('L', index); + } + + private void emitStoreInsn(char type, int index) { + int opcode; + switch (type) { + case 'I': opcode = Opcodes.ISTORE; break; + case 'J': opcode = Opcodes.LSTORE; break; + case 'F': opcode = Opcodes.FSTORE; break; + case 'D': opcode = Opcodes.DSTORE; break; + case 'L': opcode = Opcodes.ASTORE; break; + default: + throw new InternalError("unknown type: " + type); + } + mv.visitVarInsn(opcode, localsMap[index]); + } + private void emitAstoreInsn(int index) { + emitStoreInsn('L', index); + } + + /** + * Emit a boxing call. + * + * @param type primitive type class to box. + */ + private void emitBoxing(Class type) { + Wrapper wrapper = Wrapper.forPrimitiveType(type); + String owner = "java/lang/" + wrapper.wrapperType().getSimpleName(); + String name = "valueOf"; + String desc = "(" + wrapper.basicTypeChar() + ")L" + owner + ";"; + mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc); + } + + /** + * Emit an unboxing call (plus preceding checkcast). + * + * @param type wrapper type class to unbox. + */ + private void emitUnboxing(Class type) { + Wrapper wrapper = Wrapper.forWrapperType(type); + String owner = "java/lang/" + wrapper.wrapperType().getSimpleName(); + String name = wrapper.primitiveSimpleName() + "Value"; + String desc = "()" + wrapper.basicTypeChar(); + mv.visitTypeInsn(Opcodes.CHECKCAST, owner); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc); + } + + /** + * Emit an implicit conversion. + * + * @param ptype type of value present on stack + * @param pclass type of value required on stack + */ + private void emitImplicitConversion(char ptype, Class pclass) { + switch (ptype) { + case 'L': + if (VerifyType.isNullConversion(Object.class, 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.INVOKEVIRTUAL, CLS, "cast", LL_SIG); + if (pclass.isArray()) + mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY); + } + return; + case 'I': + if (!VerifyType.isNullConversion(int.class, pclass)) + emitPrimCast(ptype, Wrapper.basicTypeChar(pclass)); + return; + case 'J': + assert(pclass == long.class); + return; + case 'F': + assert(pclass == float.class); + return; + case 'D': + assert(pclass == double.class); + return; + } + throw new InternalError("bad implicit conversion: tc="+ptype+": "+pclass); + } + + /** + * Emits an actual return instruction conforming to the given return type. + */ + private void emitReturnInsn(Class type) { + int opcode; + switch (Wrapper.basicTypeChar(type)) { + case 'I': opcode = Opcodes.IRETURN; break; + case 'J': opcode = Opcodes.LRETURN; break; + case 'F': opcode = Opcodes.FRETURN; break; + case 'D': opcode = Opcodes.DRETURN; break; + case 'L': opcode = Opcodes.ARETURN; break; + case 'V': opcode = Opcodes.RETURN; break; + default: + throw new InternalError("unknown return type: " + type); + } + mv.visitInsn(opcode); + } + + private static String getInternalName(Class c) { + assert(VerifyAccess.isTypeVisible(c, Object.class)); + return c.getName().replace('.', '/'); + } + + /** + * Generate customized bytecode for a given LambdaForm. + */ + static MemberName generateCustomizedCode(LambdaForm form, MethodType invokerType) { + InvokerBytecodeGenerator g = new InvokerBytecodeGenerator("MH", form, invokerType); + return g.loadMethod(g.generateCustomizedCodeBytes()); + } + + /** + * Generate an invoker method for the passed {@link LambdaForm}. + */ + private byte[] generateCustomizedCodeBytes() { + classFilePrologue(); + + // Suppress this method in backtraces displayed to the user. + mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true); + + // Mark this method as a compiled LambdaForm + mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Compiled;", true); + + // Force inlining of this invoker method. + mv.visitAnnotation("Ljava/lang/invoke/ForceInline;", true); + + // iterate over the form's names, generating bytecode instructions for each + // start iterating at the first name following the arguments + for (int i = lambdaForm.arity; i < lambdaForm.names.length; i++) { + Name name = lambdaForm.names[i]; + MemberName member = name.function.member(); + + if (isSelectAlternative(member)) { + // selectAlternative idiom + // FIXME: make sure this idiom is really present! + emitSelectAlternative(name, lambdaForm.names[i + 1]); + i++; // skip MH.invokeBasic of the selectAlternative result + } else if (isStaticallyInvocable(member)) { + emitStaticInvoke(member, name); + } else { + emitInvoke(name); + } + + // 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') { + // non-void: actually assign + emitStoreInsn(name.type, name.index()); + } + } + + // return statement + emitReturn(); + + classFileEpilogue(); + bogusMethod(lambdaForm); + + final byte[] classFile = cw.toByteArray(); + maybeDump(className, classFile); + return classFile; + } + + /** + * Emit an invoke for the given name. + */ + void emitInvoke(Name name) { + if (true) { + // push receiver + MethodHandle target = name.function.resolvedHandle; + assert(target != null) : name.exprString(); + mv.visitLdcInsn(constantPlaceholder(target)); + mv.visitTypeInsn(Opcodes.CHECKCAST, MH); + } else { + // load receiver + emitAloadInsn(0); + mv.visitTypeInsn(Opcodes.CHECKCAST, MH); + 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); + } + + // invocation + MethodType type = name.function.methodType(); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.basicType().toMethodDescriptorString()); + } + + static private Class[] STATICALLY_INVOCABLE_PACKAGES = { + // Sample classes from each package we are willing to bind to statically: + java.lang.Object.class, + java.util.Arrays.class, + sun.misc.Unsafe.class + //MethodHandle.class already covered + }; + + static boolean isStaticallyInvocable(MemberName member) { + if (member == null) return false; + if (member.isConstructor()) return false; + Class cls = member.getDeclaringClass(); + if (cls.isArray() || cls.isPrimitive()) + return false; // FIXME + if (cls.isAnonymousClass() || cls.isLocalClass()) + return false; // inner class of some sort + if (cls.getClassLoader() != MethodHandle.class.getClassLoader()) + return false; // not on BCP + MethodType mtype = member.getMethodOrFieldType(); + if (!isStaticallyNameable(mtype.returnType())) + return false; + for (Class ptype : mtype.parameterArray()) + if (!isStaticallyNameable(ptype)) + return false; + if (!member.isPrivate() && VerifyAccess.isSamePackage(MethodHandle.class, cls)) + return true; // in java.lang.invoke package + if (member.isPublic() && isStaticallyNameable(cls)) + return true; + return false; + } + + static boolean isStaticallyNameable(Class cls) { + while (cls.isArray()) + cls = cls.getComponentType(); + if (cls.isPrimitive()) + return true; // int[].class, for example + // could use VerifyAccess.isClassAccessible but the following is a safe approximation + if (cls.getClassLoader() != Object.class.getClassLoader()) + return false; + if (VerifyAccess.isSamePackage(MethodHandle.class, cls)) + return true; + if (!Modifier.isPublic(cls.getModifiers())) + return false; + for (Class pkgcls : STATICALLY_INVOCABLE_PACKAGES) { + if (VerifyAccess.isSamePackage(pkgcls, cls)) + return true; + } + return false; + } + + /** + * 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()); + String mname = member.getName(); + String mtype; + byte refKind = member.getReferenceKind(); + if (refKind == REF_invokeSpecial) { + // in order to pass the verifier, we need to convert this to invokevirtual in all cases + assert(member.canBeStaticallyBound()) : member; + refKind = REF_invokeVirtual; + } + + if (member.getDeclaringClass().isInterface() && refKind == REF_invokeVirtual) { + // Methods from Object declared in an interface can be resolved by JVM to invokevirtual kind. + // Need to convert it back to invokeinterface to pass verification and make the invocation works as expected. + refKind = REF_invokeInterface; + } + + // push arguments + for (int i = 0; i < name.arguments.length; i++) { + emitPushArgument(name, i); + } + + // invocation + if (member.isMethod()) { + mtype = member.getMethodType().toMethodDescriptorString(); + mv.visitMethodInsn(refKindOpcode(refKind), cname, mname, mtype, + member.getDeclaringClass().isInterface()); + } else { + mtype = MethodType.toFieldDescriptorString(member.getFieldType()); + mv.visitFieldInsn(refKindOpcode(refKind), cname, mname, mtype); + } + } + int refKindOpcode(byte refKind) { + switch (refKind) { + case REF_invokeVirtual: return Opcodes.INVOKEVIRTUAL; + case REF_invokeStatic: return Opcodes.INVOKESTATIC; + case REF_invokeSpecial: return Opcodes.INVOKESPECIAL; + case REF_invokeInterface: return Opcodes.INVOKEINTERFACE; + case REF_getField: return Opcodes.GETFIELD; + case REF_putField: return Opcodes.PUTFIELD; + case REF_getStatic: return Opcodes.GETSTATIC; + case REF_putStatic: return Opcodes.PUTSTATIC; + } + throw new InternalError("refKind="+refKind); + } + + /** + * Check if MemberName is a call to MethodHandleImpl.selectAlternative. + */ + private boolean isSelectAlternative(MemberName member) { + return member != null && + member.getDeclaringClass() == MethodHandleImpl.class && + member.getName().equals("selectAlternative"); + } + + /** + * Emit bytecode for the selectAlternative idiom. + * + * The pattern looks like (Cf. MethodHandleImpl.makeGuardWithTest): + *

{@code
+     *   Lambda(a0:L,a1:I)=>{
+     *     t2:I=foo.test(a1:I);
+     *     t3:L=MethodHandleImpl.selectAlternative(t2:I,(MethodHandle(int)int),(MethodHandle(int)int));
+     *     t4:I=MethodHandle.invokeBasic(t3:L,a1:I);t4:I}
+     * }
+ */ + private void emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) { + MethodType type = selectAlternativeName.function.methodType(); + + Name receiver = (Name) invokeBasicName.arguments[0]; + + Label L_fallback = new Label(); + Label L_done = new Label(); + + // load test result + emitPushArgument(selectAlternativeName, 0); + mv.visitInsn(Opcodes.ICONST_1); + + // if_icmpne L_fallback + mv.visitJumpInsn(Opcodes.IF_ICMPNE, L_fallback); + + // invoke selectAlternativeName.arguments[1] + MethodHandle target = (MethodHandle) selectAlternativeName.arguments[1]; + emitPushArgument(selectAlternativeName, 1); // get 2nd argument of selectAlternative + emitAstoreInsn(receiver.index()); // store the MH in the receiver slot + emitInvoke(invokeBasicName); + + // goto L_done + mv.visitJumpInsn(Opcodes.GOTO, L_done); + + // L_fallback: + mv.visitLabel(L_fallback); + + // invoke selectAlternativeName.arguments[2] + MethodHandle fallback = (MethodHandle) selectAlternativeName.arguments[2]; + emitPushArgument(selectAlternativeName, 2); // get 3rd argument of selectAlternative + emitAstoreInsn(receiver.index()); // store the MH in the receiver slot + emitInvoke(invokeBasicName); + + // L_done: + mv.visitLabel(L_done); + } + + private void emitPushArgument(Name name, int paramIndex) { + Object arg = name.arguments[paramIndex]; + char ptype = name.function.parameterType(paramIndex); + MethodType mtype = name.function.methodType(); + if (arg instanceof Name) { + Name n = (Name) arg; + emitLoadInsn(n.type, n.index()); + emitImplicitConversion(n.type, mtype.parameterType(paramIndex)); + } else if ((arg == null || arg instanceof String) && ptype == 'L') { + emitConst(arg); + } else { + if (Wrapper.isWrapperType(arg.getClass()) && ptype != 'L') { + emitConst(arg); + } else { + mv.visitLdcInsn(constantPlaceholder(arg)); + emitImplicitConversion('L', mtype.parameterType(paramIndex)); + } + } + } + + /** + * Emits a return statement from a LF invoker. If required, the result type is cast to the correct return type. + */ + private void emitReturn() { + // return statement + if (lambdaForm.result == -1) { + // void + mv.visitInsn(Opcodes.RETURN); + } else { + LambdaForm.Name rn = lambdaForm.names[lambdaForm.result]; + char rtype = Wrapper.basicTypeChar(invokerType.returnType()); + + // put return value on the stack if it is not already there + if (lambdaForm.result != lambdaForm.names.length - 1) { + emitLoadInsn(rn.type, lambdaForm.result); + } + + // potentially generate cast + // rtype is the return type of the invoker - generated code must conform to this + // rn.type is the type of the result Name in the LF + if (rtype != rn.type) { + // need cast + if (rtype == 'L') { + // possibly cast the primitive to the correct type for boxing + char boxedType = Wrapper.forWrapperType(invokerType.returnType()).basicTypeChar(); + if (boxedType != rn.type) { + emitPrimCast(rn.type, boxedType); + } + // cast primitive to reference ("boxing") + emitBoxing(invokerType.returnType()); + } else { + // to-primitive cast + if (rn.type != 'L') { + // prim-to-prim cast + emitPrimCast(rn.type, rtype); + } else { + // ref-to-prim cast ("unboxing") + throw new InternalError("no ref-to-prim (unboxing) casts supported right now"); + } + } + } + + // generate actual return statement + emitReturnInsn(invokerType.returnType()); + } + } + + /** + * Emit a type conversion bytecode casting from "from" to "to". + */ + private void emitPrimCast(char from, char to) { + // Here's how. + // - indicates forbidden + // <-> indicates implicit + // to ----> boolean byte short char int long float double + // from boolean <-> - - - - - - - + // byte - <-> i2s i2c <-> i2l i2f i2d + // short - i2b <-> i2c <-> i2l i2f i2d + // char - i2b i2s <-> <-> i2l i2f i2d + // int - i2b i2s i2c <-> i2l i2f i2d + // long - l2i,i2b l2i,i2s l2i,i2c l2i <-> l2f l2d + // float - f2i,i2b f2i,i2s f2i,i2c f2i f2l <-> f2d + // double - d2i,i2b d2i,i2s d2i,i2c d2i d2l d2f <-> + if (from == to) { + // no cast required, should be dead code anyway + return; + } + Wrapper wfrom = Wrapper.forBasicType(from); + Wrapper wto = Wrapper.forBasicType(to); + if (wfrom.isSubwordOrInt()) { + // cast from {byte,short,char,int} to anything + emitI2X(to); + } else { + // cast from {long,float,double} to anything + if (wto.isSubwordOrInt()) { + // cast to {byte,short,char,int} + emitX2I(from); + if (wto.bitWidth() < 32) { + // targets other than int require another conversion + emitI2X(to); + } + } else { + // cast to {long,float,double} - this is verbose + boolean error = false; + switch (from) { + case 'J': + if (to == 'F') { mv.visitInsn(Opcodes.L2F); } + else if (to == 'D') { mv.visitInsn(Opcodes.L2D); } + else error = true; + break; + case 'F': + if (to == 'J') { mv.visitInsn(Opcodes.F2L); } + else if (to == 'D') { mv.visitInsn(Opcodes.F2D); } + else error = true; + break; + case 'D': + if (to == 'J') { mv.visitInsn(Opcodes.D2L); } + else if (to == 'F') { mv.visitInsn(Opcodes.D2F); } + else error = true; + break; + default: + error = true; + break; + } + if (error) { + throw new IllegalStateException("unhandled prim cast: " + from + "2" + to); + } + } + } + } + + private void emitI2X(char type) { + switch (type) { + case 'B': mv.visitInsn(Opcodes.I2B); break; + case 'S': mv.visitInsn(Opcodes.I2S); break; + case 'C': mv.visitInsn(Opcodes.I2C); break; + case 'I': /* naught */ break; + case 'J': mv.visitInsn(Opcodes.I2L); break; + case 'F': mv.visitInsn(Opcodes.I2F); break; + case 'D': mv.visitInsn(Opcodes.I2D); break; + case 'Z': + // For compatibility with ValueConversions and explicitCastArguments: + mv.visitInsn(Opcodes.ICONST_1); + mv.visitInsn(Opcodes.IAND); + break; + default: throw new InternalError("unknown type: " + type); + } + } + + private void emitX2I(char type) { + switch (type) { + case 'J': mv.visitInsn(Opcodes.L2I); break; + case 'F': mv.visitInsn(Opcodes.F2I); break; + case 'D': mv.visitInsn(Opcodes.D2I); break; + default: throw new InternalError("unknown type: " + type); + } + } + + private static String basicTypeCharSignature(String prefix, MethodType type) { + StringBuilder buf = new StringBuilder(prefix); + for (Class ptype : type.parameterList()) + buf.append(Wrapper.forBasicType(ptype).basicTypeChar()); + buf.append('_').append(Wrapper.forBasicType(type.returnType()).basicTypeChar()); + return buf.toString(); + } + + /** + * Generate bytecode for a LambdaForm.vmentry which calls interpretWithArguments. + */ + static MemberName generateLambdaFormInterpreterEntryPoint(String sig) { + assert(LambdaForm.isValidSignature(sig)); + //System.out.println("generateExactInvoker "+sig); + // compute method type + // first parameter and return type + char tret = LambdaForm.signatureReturn(sig); + MethodType type = MethodType.methodType(LambdaForm.typeClass(tret), MethodHandle.class); + // other parameter types + int arity = LambdaForm.signatureArity(sig); + for (int i = 1; i < arity; i++) { + type = type.appendParameterTypes(LambdaForm.typeClass(sig.charAt(i))); + } + InvokerBytecodeGenerator g = new InvokerBytecodeGenerator("LFI", "interpret_"+tret, type); + return g.loadMethod(g.generateLambdaFormInterpreterEntryPointBytes()); + } + + private byte[] generateLambdaFormInterpreterEntryPointBytes() { + classFilePrologue(); + + // Suppress this method in backtraces displayed to the user. + mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true); + + // Don't inline the interpreter entry. + mv.visitAnnotation("Ljava/lang/invoke/DontInline;", true); + + // create parameter array + emitIconstInsn(invokerType.parameterCount()); + mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + + // fill parameter array + for (int i = 0; i < invokerType.parameterCount(); i++) { + Class ptype = invokerType.parameterType(i); + mv.visitInsn(Opcodes.DUP); + emitIconstInsn(i); + emitLoadInsn(Wrapper.basicTypeChar(ptype), i); + // box if primitive type + if (ptype.isPrimitive()) { + emitBoxing(ptype); + } + mv.visitInsn(Opcodes.AASTORE); + } + // invoke + emitAloadInsn(0); + mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", "Ljava/lang/invoke/LambdaForm;"); + mv.visitInsn(Opcodes.SWAP); // swap form and array; avoid local variable + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, LF, "interpretWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;"); + + // maybe unbox + Class rtype = invokerType.returnType(); + if (rtype.isPrimitive() && rtype != void.class) { + emitUnboxing(Wrapper.asWrapperType(rtype)); + } + + // return statement + emitReturnInsn(rtype); + + classFileEpilogue(); + bogusMethod(invokerType); + + final byte[] classFile = cw.toByteArray(); + maybeDump(className, classFile); + return classFile; + } + + /** + * Generate bytecode for a NamedFunction invoker. + */ + static MemberName generateNamedFunctionInvoker(MethodTypeForm typeForm) { + MethodType invokerType = LambdaForm.NamedFunction.INVOKER_METHOD_TYPE; + String invokerName = basicTypeCharSignature("invoke_", typeForm.erasedType()); + InvokerBytecodeGenerator g = new InvokerBytecodeGenerator("NFI", invokerName, invokerType); + return g.loadMethod(g.generateNamedFunctionInvokerImpl(typeForm)); + } + + static int nfi = 0; + + private byte[] generateNamedFunctionInvokerImpl(MethodTypeForm typeForm) { + MethodType dstType = typeForm.erasedType(); + classFilePrologue(); + + // Suppress this method in backtraces displayed to the user. + mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true); + + // Force inlining of this invoker method. + mv.visitAnnotation("Ljava/lang/invoke/ForceInline;", true); + + // Load receiver + emitAloadInsn(0); + + // Load arguments from array + for (int i = 0; i < dstType.parameterCount(); i++) { + emitAloadInsn(1); + emitIconstInsn(i); + mv.visitInsn(Opcodes.AALOAD); + + // Maybe unbox + Class dptype = dstType.parameterType(i); + if (dptype.isPrimitive()) { + Class sptype = dstType.basicType().wrap().parameterType(i); + Wrapper dstWrapper = Wrapper.forBasicType(dptype); + Wrapper srcWrapper = dstWrapper.isSubwordOrInt() ? Wrapper.INT : dstWrapper; // narrow subword from int + emitUnboxing(srcWrapper.wrapperType()); + emitPrimCast(srcWrapper.basicTypeChar(), dstWrapper.basicTypeChar()); + } + } + + // Invoke + String targetDesc = dstType.basicType().toMethodDescriptorString(); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", targetDesc); + + // Box primitive types + Class rtype = dstType.returnType(); + if (rtype != void.class && rtype.isPrimitive()) { + Wrapper srcWrapper = Wrapper.forBasicType(rtype); + Wrapper dstWrapper = srcWrapper.isSubwordOrInt() ? Wrapper.INT : srcWrapper; // widen subword to int + // boolean casts not allowed + emitPrimCast(srcWrapper.basicTypeChar(), dstWrapper.basicTypeChar()); + emitBoxing(dstWrapper.primitiveType()); + } + + // If the return type is void we return a null reference. + if (rtype == void.class) { + mv.visitInsn(Opcodes.ACONST_NULL); + } + emitReturnInsn(Object.class); // NOTE: NamedFunction invokers always return a reference value. + + classFileEpilogue(); + bogusMethod(dstType); + + final byte[] classFile = cw.toByteArray(); + maybeDump(className, classFile); + return classFile; + } + + /** + * Emit a bogus method that just loads some string constants. This is to get the constants into the constant pool + * for debugging purposes. + */ + private void bogusMethod(Object... os) { + if (DUMP_CLASS_FILES) { + mv = cw.visitMethod(Opcodes.ACC_STATIC, "dummy", "()V", null, null); + for (Object o : os) { + mv.visitLdcInsn(o.toString()); + mv.visitInsn(Opcodes.POP); + } + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + } +}