Merge "CF backend: Implement InvokeCustom (JVM InvokeDynamic)"
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index ea6eb54..39d916a 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfMonitor;
@@ -228,6 +229,13 @@
     appendMethod(invoke.getMethod());
   }
 
+  public void print(CfInvokeDynamic invoke) {
+    indent();
+    builder.append(opcodeName(Opcodes.INVOKEDYNAMIC)).append(' ');
+    builder.append(invoke.getCallSite().methodName);
+    builder.append(invoke.getCallSite().methodProto.toDescriptorString());
+  }
+
   public void print(CfFrame frame) {
     StringBuilder builder = new StringBuilder("frame: [");
     String separator = "";
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 5a07602..86903fe 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -14,7 +14,7 @@
   private final int opcode;
 
   public CfInvoke(int opcode, DexMethod method) {
-    assert Opcodes.INVOKEVIRTUAL <= opcode && opcode <= Opcodes.INVOKEDYNAMIC;
+    assert Opcodes.INVOKEVIRTUAL <= opcode && opcode <= Opcodes.INVOKEINTERFACE;
     this.opcode = opcode;
     this.method = method;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
new file mode 100644
index 0000000..43e6133
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueMethodType;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import java.util.List;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+
+public class CfInvokeDynamic extends CfInstruction {
+
+  private final DexCallSite callSite;
+
+  public CfInvokeDynamic(DexCallSite callSite) {
+    this.callSite = callSite;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor) {
+    DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
+    List<DexValue> bootstrapArgs = callSite.bootstrapArgs;
+    Object[] bsmArgs = new Object[bootstrapArgs.size()];
+    for (int i = 0; i < bootstrapArgs.size(); i++) {
+      bsmArgs[i] = decodeBootstrapArgument(bootstrapArgs.get(i));
+    }
+    Handle bsmHandle = bootstrapMethod.toAsmHandle();
+    visitor.visitInvokeDynamicInsn(
+        callSite.methodName.toString(),
+        callSite.methodProto.toDescriptorString(),
+        bsmHandle,
+        bsmArgs);
+  }
+
+  private Object decodeBootstrapArgument(DexValue dexValue) {
+    if (dexValue instanceof DexValueInt) {
+      return ((DexValueInt) dexValue).getValue();
+    } else if (dexValue instanceof DexValueLong) {
+      return ((DexValueLong) dexValue).getValue();
+    } else if (dexValue instanceof DexValueFloat) {
+      return ((DexValueFloat) dexValue).getValue();
+    } else if (dexValue instanceof DexValueDouble) {
+      return ((DexValueDouble) dexValue).getValue();
+    } else if (dexValue instanceof DexValueString) {
+      return ((DexValueString) dexValue).getValue();
+    } else if (dexValue instanceof DexValueType) {
+      return Type.getType(((DexValueType) dexValue).value.toDescriptorString());
+    } else if (dexValue instanceof DexValueMethodType) {
+      return Type.getMethodType(((DexValueMethodType) dexValue).value.toDescriptorString());
+    } else if (dexValue instanceof DexValueMethodHandle) {
+      return ((DexValueMethodHandle) dexValue).value.toAsmHandle();
+    } else {
+      throw new Unreachable(
+          "Unsupported bootstrap argument of type " + dexValue.getClass().getSimpleName());
+    }
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  public DexCallSite getCallSite() {
+    return callSite;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 38d83ed..0579362 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -3,9 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.code.InvokeCustomRange;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -70,6 +74,11 @@
   }
 
   @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfInvokeDynamic(getCallSite()));
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.isInvokeCustom() && callSite == other.asInvokeCustom().callSite;
   }
@@ -95,4 +104,30 @@
   public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
     return Constraint.NEVER;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    // Essentially the same as InvokeMethod but with call site's method proto
+    // instead of a static called method.
+    helper.loadInValues(this, it);
+    if (getCallSite().methodProto.returnType.isVoidType()) {
+      return;
+    }
+    if (outValue == null) {
+      helper.popOutType(getCallSite().methodProto.returnType, this, it);
+    } else {
+      assert outValue.isUsed();
+      helper.storeOutValue(this, it);
+    }
+  }
+
+  @Override
+  public boolean hasInvariantVerificationType() {
+    return true;
+  }
+
+  @Override
+  public DexType computeVerificationType(TypeVerificationHelper helper) {
+    return getCallSite().methodProto.returnType;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTest.java b/src/test/java/com/android/tools/r8/cf/LambdaTest.java
new file mode 100644
index 0000000..983f1b7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTest.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf;
+
+import java.util.function.Function;
+
+public class LambdaTest {
+  public static void main(String[] args) {
+    twicePrint(args.length, i -> i + 21);
+  }
+
+  private static void twicePrint(int v, Function<Integer, Integer> f) {
+    System.out.println(f.andThen(f).apply(v));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
new file mode 100644
index 0000000..21d0292
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.JarCode;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.DexInspector;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.InvokeDynamicInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+public class LambdaTestRunner {
+
+  private static final Class<?> CLASS = LambdaTest.class;
+  private static final String METHOD = "main";
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void test() throws Exception {
+    // Test that the InvokeDynamic instruction in LambdaTest.main()
+    // is not modified by the R8 compilation.
+    // First, extract the InvokeDynamic instruction from the input class.
+    byte[] inputClass = ToolHelper.getClassAsBytes(CLASS);
+    int opcode = Opcodes.INVOKEDYNAMIC;
+    InvokeDynamicInsnNode insnInput = findFirstInMethod(inputClass, opcode);
+    // Compile with R8 and extract the InvokeDynamic instruction from the output class.
+    AndroidAppConsumers appBuilder = new AndroidAppConsumers();
+    Path outPath = temp.getRoot().toPath().resolve("out.jar");
+    R8.run(
+        R8Command.builder()
+            .setMode(CompilationMode.DEBUG)
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .setProgramConsumer(appBuilder.wrapClassFileConsumer(new ArchiveConsumer(outPath)))
+            .addClassProgramData(inputClass, Origin.unknown())
+            .build());
+    AndroidApp app = appBuilder.build();
+    InvokeDynamicInsnNode insnOutput = findFirstInMethod(app, opcode);
+    // Check that the InvokeDynamic instruction is not modified.
+    assertEquals(insnInput.name, insnOutput.name);
+    assertEquals(insnInput.desc, insnOutput.desc);
+    assertEquals(insnInput.bsm, insnOutput.bsm);
+    assertArrayEquals(insnInput.bsmArgs, insnOutput.bsmArgs);
+    // Check that execution gives the same output.
+    ProcessResult inputResult =
+        ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getName());
+    ProcessResult outputResult = ToolHelper.runJava(outPath, CLASS.getName());
+    assertEquals(inputResult.toString(), outputResult.toString());
+  }
+
+  private InvokeDynamicInsnNode findFirstInMethod(AndroidApp app, int opcode) throws Exception {
+    String returnType = "void";
+    DexInspector inspector = new DexInspector(app);
+    List<String> args = Collections.singletonList(String[].class.getTypeName());
+    DexEncodedMethod method = inspector.clazz(CLASS).method(returnType, METHOD, args).getMethod();
+    JarCode jarCode = method.getCode().asJarCode();
+    MethodNode outputMethod = jarCode.getNode();
+    return (InvokeDynamicInsnNode) findFirstInstruction(outputMethod, opcode);
+  }
+
+  private InvokeDynamicInsnNode findFirstInMethod(byte[] clazz, int opcode) {
+    MethodNode[] method = {null};
+    new ClassReader(clazz)
+        .accept(
+            new ClassNode(Opcodes.ASM6) {
+              @Override
+              public MethodVisitor visitMethod(
+                  int access, String name, String desc, String signature, String[] exceptions) {
+                if (name.equals(METHOD)) {
+                  method[0] = new MethodNode(access, name, desc, signature, exceptions);
+                  return method[0];
+                } else {
+                  return null;
+                }
+              }
+            },
+            0);
+    return (InvokeDynamicInsnNode) findFirstInstruction(method[0], opcode);
+  }
+
+  private AbstractInsnNode findFirstInstruction(MethodNode node, int opcode) {
+    assert node != null;
+    InsnList asmInsns = node.instructions;
+    for (ListIterator<AbstractInsnNode> it = asmInsns.iterator(); it.hasNext(); ) {
+      AbstractInsnNode insn = it.next();
+      if (insn.getOpcode() == opcode) {
+        return insn;
+      }
+    }
+    throw new RuntimeException("Instruction not found");
+  }
+}