Add new const-method-handle opcode

Change-Id: I6d890810b5d76a1c6292f50717643a8391805d79
diff --git a/build.gradle b/build.gradle
index 6be6012..7b83fcf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -88,6 +88,12 @@
         }
         output.resourcesDir = 'build/classes/examplesAndroidO'
     }
+    examplesAndroidP {
+        java {
+            srcDirs = ['src/test/examplesAndroidP']
+        }
+        output.resourcesDir = 'build/classes/examplesAndroidP'
+    }
     jctfCommon {
         java {
             srcDirs = [
@@ -145,6 +151,7 @@
     jctfTestsCompile 'junit:junit:4.12'
     jctfTestsCompile sourceSets.jctfCommon.output
     examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: '6.0_BETA'
+    examplesAndroidPCompile group: 'org.ow2.asm', name: 'asm', version: '6.0_BETA'
     examplesCompile 'com.google.protobuf:protobuf-lite:3.0.0'
     examplesRuntime 'com.google.protobuf:protobuf-lite:3.0.0'
     supportLibs 'com.android.support:support-v4:25.4.0'
@@ -887,6 +894,51 @@
     }
 }
 
+task buildExampleAndroidPJars {
+    dependsOn downloadDeps
+    def examplesDir = file("src/test/examplesAndroidP")
+
+    task "compile_examplesAndroidP"(type: JavaCompile) {
+        source = fileTree(dir: examplesDir, include: '**/*.java')
+        destinationDir = file("build/test/examplesAndroidP/classes")
+        classpath = sourceSets.main.compileClasspath
+        sourceCompatibility = JavaVersion.VERSION_1_8
+        targetCompatibility = JavaVersion.VERSION_1_8
+        options.compilerArgs += ["-Xlint:-options"]
+    }
+    examplesDir.eachDir { dir ->
+        def name = dir.getName();
+        def destinationDir = file("build/test/examplesAndroidP/classes");
+        if (file("src/test/examplesAndroidP/" + name + "/TestGenerator.java").isFile()) {
+            task "generate_examplesAndroidP_${name}"(type: JavaExec,
+                    dependsOn: "compile_examplesAndroidP") {
+                main = name + ".TestGenerator"
+                classpath = files(destinationDir, sourceSets.main.compileClasspath)
+                args destinationDir
+            }
+        } else {
+            task "generate_examplesAndroidP_${name}" () {}
+        }
+    }
+    examplesDir.eachDir { dir ->
+        def name = dir.getName();
+        def exampleOutputDir = file("build/test/examplesAndroidP");
+        def jarName = "${name}.jar"
+        dependsOn "jar_examplesAndroidP_${name}"
+        task "jar_examplesAndroidP_${name}"(type: Jar,
+                dependsOn: ["compile_examplesAndroidP",
+                            "generate_examplesAndroidP_${name}"]) {
+            archiveName = jarName
+            destinationDir = exampleOutputDir
+            from "build/test/examplesAndroidP/classes"  // Java 1.8 classes
+            include "**/" + name + "/**/*.class"
+            // Do not include generator into the test runtime jar, it is not useful.
+            // Otherwise, shrinking will need ASM jars.
+            exclude "**/TestGenerator*"
+        }
+    }
+}
+
 task buildExamples {
     if (OperatingSystem.current().isMacOsX() || OperatingSystem.current().isWindows()) {
         logger.lifecycle("WARNING: Testing (including building examples) is only partially supported on your " +
@@ -900,6 +952,7 @@
     dependsOn buildExampleJars
     dependsOn buildExampleAndroidNJars
     dependsOn buildExampleAndroidOJars
+    dependsOn buildExampleAndroidPJars
     def examplesDir = file("src/test/examples")
     def noDexTests = [
         "multidex",
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
new file mode 100644
index 0000000..e2c084b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2017, 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.code;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.naming.ClassNameMapper;
+import java.nio.ShortBuffer;
+
+public class ConstMethodHandle extends Format21c {
+
+  public static final int OPCODE = 0xfe;
+  public static final String NAME = "ConstMethodHandle";
+  public static final String SMALI_NAME = "const-method-handle";
+
+  ConstMethodHandle(int high, BytecodeStream stream, OffsetToObjectMapping mapping) {
+    super(high, stream, mapping.getMethodHandleMap());
+  }
+
+  public ConstMethodHandle(int register, DexMethodHandle methodHandle) {
+    super(register, methodHandle);
+  }
+
+  public DexMethodHandle getMethodHandle() {
+    return (DexMethodHandle) BBBB;
+  }
+
+  public String getName() {
+    return NAME;
+  }
+
+  public String getSmaliName() {
+    return SMALI_NAME;
+  }
+
+  public int getOpcode() {
+    return OPCODE;
+  }
+
+  public String toString(ClassNameMapper naming) {
+    return formatString("v" + AA + ", \"" + BBBB.toString() + "\"");
+  }
+
+  public String toSmaliString(ClassNameMapper naming) {
+    return formatSmaliString("v" + AA + ", \"" + BBBB.toString() + "\"");
+  }
+
+  @Override
+  public void registerUse(UseRegistry registry) {
+    registry.registerMethodHandle(getMethodHandle());
+  }
+
+  @Override
+  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+    int index = BBBB.getOffset(mapping);
+    if (index != (index & 0xffff)) {
+      throw new InternalCompilerError("MethodHandle-index overflow.");
+    }
+    super.write(dest, mapping);
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
+    builder.addConstMethodHandle(AA, (DexMethodHandle) BBBB);
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
index 3c0b67c..c4a16b5 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
@@ -4,8 +4,6 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
 import com.android.tools.r8.graph.DexValue.DexValueType;
@@ -61,54 +59,15 @@
   }
 
   static void registerCallSite(UseRegistry registry, DexCallSite callSite) {
-    InvokeCustom.registerMethodHandle(registry, callSite.bootstrapMethod);
+    registry.registerMethodHandle(callSite.bootstrapMethod);
 
     // Register bootstrap method arguments, only Type and MethodHandle need to be register.
     for (DexValue arg : callSite.bootstrapArgs) {
       if (arg instanceof DexValueType) {
         registry.registerTypeReference(((DexValueType) arg).value);
       } else if (arg instanceof DexValueMethodHandle) {
-        InvokeCustom.registerMethodHandle(registry, ((DexValueMethodHandle) arg).value);
+        registry.registerMethodHandle(((DexValueMethodHandle) arg).value);
       }
     }
   }
-
-  static void registerMethodHandle(UseRegistry registry, DexMethodHandle methodHandle) {
-    switch (methodHandle.type) {
-      case INSTANCE_GET:
-        registry.registerInstanceFieldRead(methodHandle.asField());
-        break;
-      case INSTANCE_PUT:
-        registry.registerInstanceFieldWrite(methodHandle.asField());
-        break;
-      case STATIC_GET:
-        registry.registerStaticFieldRead(methodHandle.asField());
-        break;
-      case STATIC_PUT:
-        registry.registerStaticFieldWrite(methodHandle.asField());
-        break;
-      case INVOKE_INSTANCE:
-        registry.registerInvokeVirtual(methodHandle.asMethod());
-        break;
-      case INVOKE_STATIC:
-        registry.registerInvokeStatic(methodHandle.asMethod());
-        break;
-      case INVOKE_CONSTRUCTOR:
-        DexMethod method = methodHandle.asMethod();
-        registry.registerNewInstance(method.getHolder());
-        registry.registerInvokeDirect(method);
-        break;
-      case INVOKE_INTERFACE:
-        registry.registerInvokeInterface(methodHandle.asMethod());
-        break;
-      case INVOKE_SUPER:
-        registry.registerInvokeSuper(methodHandle.asMethod());
-        break;
-      case INVOKE_DIRECT:
-        registry.registerInvokeDirect(methodHandle.asMethod());
-        break;
-      default:
-        throw new AssertionError();
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index eeba4e8..d64a893 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.naming.NamingLens;
 
-public class DexMethodHandle extends IndexedDexItem {
+public class DexMethodHandle extends IndexedDexItem implements
+    PresortedComparable<DexMethodHandle> {
 
   public enum MethodHandleType {
     STATIC_PUT((short) 0x00),
@@ -190,4 +192,51 @@
     assert isFieldHandle();
     return (DexField) fieldOrMethod;
   }
+
+  @Override
+  public int slowCompareTo(DexMethodHandle other) {
+    int result = type.getValue() - other.type.getValue();
+    if (result == 0) {
+      if (isFieldHandle()) {
+        result = asField().slowCompareTo(other.asField());
+      } else {
+        assert isMethodHandle();
+        result = asMethod().slowCompareTo(other.asMethod());
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public int slowCompareTo(DexMethodHandle other, NamingLens namingLens) {
+    int result = type.getValue() - other.type.getValue();
+    if (result == 0) {
+      if (isFieldHandle()) {
+        result = asField().slowCompareTo(other.asField(), namingLens);
+      } else {
+        assert isMethodHandle();
+        result = asMethod().slowCompareTo(other.asMethod(), namingLens);
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public int layeredCompareTo(DexMethodHandle other, NamingLens namingLens) {
+    int result = type.getValue() - other.type.getValue();
+    if (result == 0) {
+      if (isFieldHandle()) {
+        result = asField().layeredCompareTo(other.asField(), namingLens);
+      } else {
+        assert isMethodHandle();
+        result = asMethod().layeredCompareTo(other.asMethod(), namingLens);
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public int compareTo(DexMethodHandle other) {
+    return sortedCompareTo(other.getSortedIndex());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 45824c2..78b2f56 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -26,4 +26,43 @@
   public abstract boolean registerStaticFieldWrite(DexField field);
 
   public abstract boolean registerTypeReference(DexType type);
+
+  public void registerMethodHandle(DexMethodHandle methodHandle) {
+    switch (methodHandle.type) {
+      case INSTANCE_GET:
+        registerInstanceFieldRead(methodHandle.asField());
+        break;
+      case INSTANCE_PUT:
+        registerInstanceFieldWrite(methodHandle.asField());
+        break;
+      case STATIC_GET:
+        registerStaticFieldRead(methodHandle.asField());
+        break;
+      case STATIC_PUT:
+        registerStaticFieldWrite(methodHandle.asField());
+        break;
+      case INVOKE_INSTANCE:
+        registerInvokeVirtual(methodHandle.asMethod());
+        break;
+      case INVOKE_STATIC:
+        registerInvokeStatic(methodHandle.asMethod());
+        break;
+      case INVOKE_CONSTRUCTOR:
+        DexMethod method = methodHandle.asMethod();
+        registerNewInstance(method.getHolder());
+        registerInvokeDirect(method);
+        break;
+      case INVOKE_INTERFACE:
+        registerInvokeInterface(methodHandle.asMethod());
+        break;
+      case INVOKE_SUPER:
+        registerInvokeSuper(methodHandle.asMethod());
+        break;
+      case INVOKE_DIRECT:
+        registerInvokeDirect(methodHandle.asMethod());
+        break;
+      default:
+        throw new AssertionError();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
new file mode 100644
index 0000000..108eb32
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2017, 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.ir.code;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+
+public class ConstMethodHandle extends ConstInstruction {
+
+  private final DexMethodHandle methodHandle;
+
+  public ConstMethodHandle(Value dest, DexMethodHandle methodHandle) {
+    super(dest);
+    dest.markNeverNull();
+    this.methodHandle = methodHandle;
+  }
+
+  public Value dest() {
+    return outValue;
+  }
+
+  public DexMethodHandle getValue() {
+    return methodHandle;
+  }
+
+  @Override
+  public void buildDex(DexBuilder builder) {
+    int dest = builder.allocatedRegister(dest(), getNumber());
+    builder.add(this, new com.android.tools.r8.code.ConstMethodHandle(dest, methodHandle));
+  }
+
+  @Override
+  public boolean identicalNonValueParts(Instruction other) {
+    return other.asConstMethodHandle().methodHandle == methodHandle;
+  }
+
+  @Override
+  public int compareNonValueParts(Instruction other) {
+    return methodHandle.slowCompareTo(other.asConstMethodHandle().methodHandle);
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    assert false : "ConstMethodHandle has no register arguments.";
+    return 0;
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    return Constants.U8BIT_MAX;
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + " \"" + methodHandle + "\"";
+  }
+
+  @Override
+  public boolean instructionTypeCanThrow() {
+    return true;
+  }
+
+  @Override
+  public boolean isOutConstant() {
+    return true;
+  }
+
+  @Override
+  public boolean isConstMethodHandle() {
+    return true;
+  }
+
+  @Override
+  public ConstMethodHandle asConstMethodHandle() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 21c2abf..24c3925 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -450,6 +450,14 @@
     return null;
   }
 
+  public boolean isConstMethodHandle() {
+    return false;
+  }
+
+  public ConstMethodHandle asConstMethodHandle() {
+    return null;
+  }
+
   public boolean isConstString() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 35cd7fa..c84adcb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.code.Cmp;
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.ConstMethodHandle;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.ConstType;
@@ -799,6 +800,19 @@
     add(instruction);
   }
 
+  public void addConstMethodHandle(int dest, DexMethodHandle methodHandle)
+      throws ApiLevelException {
+    if (!options.canUseConstantMethodHandle()) {
+      throw new ApiLevelException(
+          AndroidApiLevel.P,
+          "Const-method-handle",
+          null /* sourceString */);
+    }
+    Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.CAN_THROW);
+    ConstMethodHandle instruction = new ConstMethodHandle(out, methodHandle);
+    add(instruction);
+  }
+
   public void addConstString(int dest, DexString string) {
     Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.CAN_THROW);
     ConstString instruction = new ConstString(out, string);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index e44ee80..a4203a4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -142,6 +142,7 @@
   static final Type STRING_TYPE = Type.getObjectType("java/lang/String");
   static final Type INT_ARRAY_TYPE = Type.getObjectType(INT_ARRAY_DESC);
   static final Type THROWABLE_TYPE = Type.getObjectType("java/lang/Throwable");
+  static final Type METHOD_HANDLE_TYPE = Type.getObjectType("java/lang/invoke/MethodHandle");
 
   private static final int[] NO_TARGETS = {};
 
@@ -619,7 +620,7 @@
       case Opcodes.LDC: {
         // const-class and const-string* may throw in dex.
         LdcInsnNode ldc = (LdcInsnNode) insn;
-        return ldc.cst instanceof String || ldc.cst instanceof Type;
+        return ldc.cst instanceof String || ldc.cst instanceof Type || ldc.cst instanceof Handle;
       }
       default:
         return false;
@@ -1727,6 +1728,8 @@
       state.push(Type.INT_TYPE);
     } else if (insn.cst instanceof Float) {
       state.push(Type.FLOAT_TYPE);
+    } else if (insn.cst instanceof Handle) {
+      state.push(METHOD_HANDLE_TYPE);
     } else {
       throw new CompilationError("Unsupported constant: " + insn.cst.toString());
     }
@@ -2724,7 +2727,7 @@
     }
   }
 
-  private void build(LdcInsnNode insn, IRBuilder builder) {
+  private void build(LdcInsnNode insn, IRBuilder builder) throws ApiLevelException {
     if (insn.cst instanceof Type) {
       Type type = (Type) insn.cst;
       int dest = state.push(type);
@@ -2744,6 +2747,10 @@
     } else if (insn.cst instanceof Float) {
       int dest = state.push(Type.FLOAT_TYPE);
       builder.addFloatConst(dest, Float.floatToRawIntBits((Float) insn.cst));
+    } else if (insn.cst instanceof Handle) {
+      Handle handle = (Handle) insn.cst;
+      int dest = state.push(METHOD_HANDLE_TYPE);
+      builder.addConstMethodHandle(dest, getMethodHandle(application, handle));
     } else {
       throw new CompilationError("Unsupported constant: " + insn.cst.toString());
     }
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index 2d38bf0..c718e91 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -49,6 +49,8 @@
   public void visitLdcInsn(Object cst) {
     if (cst instanceof Type) {
       registry.registerTypeReference(application.getType((Type) cst));
+    } else if (cst instanceof Handle) {
+      registerMethodHandleType((Handle) cst);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index c8308a9..68a6502 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -348,6 +348,10 @@
     return minApiLevel >= AndroidApiLevel.O.getLevel();
   }
 
+  public boolean canUseConstantMethodHandle() {
+    return minApiLevel >= AndroidApiLevel.P.getLevel();
+  }
+
   public boolean canUseInvokeCustom() {
     return minApiLevel >= AndroidApiLevel.O.getLevel();
   }
diff --git a/src/test/examplesAndroidP/invokecustom/InvokeCustom.java b/src/test/examplesAndroidP/invokecustom/InvokeCustom.java
new file mode 100644
index 0000000..282b5ae
--- /dev/null
+++ b/src/test/examplesAndroidP/invokecustom/InvokeCustom.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2017, 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 invokecustom;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+
+public class InvokeCustom {
+
+  private static String staticField1 = "StaticField1";
+
+  private static void targetMethodTest1() {
+    System.out.println("Hello World!");
+  }
+
+  private static void targetMethodTest2(MethodHandle mhInvokeStatic, MethodHandle mhGetStatic)
+      throws Throwable {
+    mhInvokeStatic.invokeExact();
+    System.out.println(mhGetStatic.invoke());
+  }
+
+  public static CallSite bsmLookupStatic(MethodHandles.Lookup caller, String name, MethodType type)
+      throws NoSuchMethodException, IllegalAccessException {
+    final MethodHandles.Lookup lookup = MethodHandles.lookup();
+    final MethodHandle targetMH = lookup.findStatic(lookup.lookupClass(), name, type);
+    return new ConstantCallSite(targetMH.asType(type));
+  }
+
+}
diff --git a/src/test/examplesAndroidP/invokecustom/TestGenerator.java b/src/test/examplesAndroidP/invokecustom/TestGenerator.java
new file mode 100644
index 0000000..b9a5126
--- /dev/null
+++ b/src/test/examplesAndroidP/invokecustom/TestGenerator.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2017, 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 invokecustom;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class TestGenerator {
+
+  private final Path classNamePath;
+
+  public static void main(String[] args) throws IOException {
+    assert args.length == 1;
+    TestGenerator testGenerator = new TestGenerator(Paths.get(args[0],
+        TestGenerator.class.getPackage().getName(), InvokeCustom.class.getSimpleName() + ".class"));
+    testGenerator.generateTests();
+  }
+
+  public TestGenerator(Path classNamePath) {
+    this.classNamePath = classNamePath;
+  }
+
+  private void generateTests() throws IOException {
+    ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile()));
+    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+    cr.accept(
+        new ClassVisitor(Opcodes.ASM6, cw) {
+          @Override
+          public void visitEnd() {
+            generateMethodTest1(cw);
+            generateMethodMain(cw);
+            super.visitEnd();
+          }
+        }, 0);
+    new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray());
+  }
+
+  /* Generate main method that only call all test methods. */
+  private void generateMethodMain(ClassVisitor cv) {
+    MethodVisitor mv = cv.visitMethod(
+            Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+    mv.visitMethodInsn(
+        Opcodes.INVOKESTATIC, Type.getInternalName(InvokeCustom.class), "test1", "()V", false);
+    mv.visitInsn(Opcodes.RETURN);
+    mv.visitMaxs(-1, -1);
+  }
+
+  /**
+   *  Generate test with an invokedynamic, a static bootstrap method without extra args and
+   *  args to the target method.
+   */
+  private void generateMethodTest1(ClassVisitor cv) {
+    MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test1", "()V",
+        null, null);
+    MethodType mt = MethodType.methodType(
+            CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
+    Handle bootstrap = new Handle( Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
+        "bsmLookupStatic", mt.toMethodDescriptorString(), false);
+    mv.visitLdcInsn(new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
+        "targetMethodTest1", "()V", false));
+    mv.visitLdcInsn(new Handle(Opcodes.H_GETSTATIC, Type.getInternalName(InvokeCustom.class),
+        "staticField1", "Ljava/lang/String;", false));
+    mv.visitInvokeDynamicInsn("targetMethodTest2",
+        "(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V",
+        bootstrap);
+    mv.visitInsn(Opcodes.RETURN);
+    mv.visitMaxs(-1, -1);
+  }
+}
diff --git a/src/test/examplesAndroidP/invokecustom/keep-rules.txt b/src/test/examplesAndroidP/invokecustom/keep-rules.txt
new file mode 100644
index 0000000..dbc21fc
--- /dev/null
+++ b/src/test/examplesAndroidP/invokecustom/keep-rules.txt
@@ -0,0 +1,16 @@
+# Copyright (c) 2017, 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.
+
+# Keep the application entry point and the target methods of invoke-custom because these methods
+# can not be known at compile time. Get rid of everything that is not reachable from there.
+-keep public class invokecustom.InvokeCustom {
+  public static void main(...);
+}
+
+-keepclasseswithmembers class * {
+  *** targetMethodTest*(...);
+}
+
+# allow access modification to enable minification
+-allowaccessmodification
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
new file mode 100644
index 0000000..dde8a9c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2017, 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;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.OutputMode;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.function.UnaryOperator;
+import org.hamcrest.core.CombinableMatcher;
+import org.hamcrest.core.IsInstanceOf;
+import org.hamcrest.core.StringContains;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.internal.matchers.ThrowableMessageMatcher;
+
+public class D8RunExamplesAndroidPTest extends RunExamplesAndroidPTest<D8Command.Builder> {
+
+  class D8TestRunner extends TestRunner<D8TestRunner> {
+
+    D8TestRunner(String testName, String packageName, String mainClass) {
+      super(testName, packageName, mainClass);
+    }
+
+    @Override
+    D8TestRunner withMinApiLevel(int minApiLevel) {
+      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+    }
+
+    D8TestRunner withClasspath(Path... classpath) {
+      return withBuilderTransformation(b -> {
+        try {
+          return b.addClasspathFiles(classpath);
+        } catch (IOException e) {
+          throw new AssertionError(e);
+        }
+      });
+    }
+
+
+    @Override
+    void build(Path inputFile, Path out) throws Throwable {
+      D8Command.Builder builder = D8Command.builder();
+      for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
+        builder = transformation.apply(builder);
+      }
+      // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
+      builder.addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(AndroidApiLevel.O.getLevel())));
+      D8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build();
+      try {
+        ToolHelper.runD8(command, this::combinedOptionConsumer);
+      } catch (Unimplemented | CompilationError | InternalCompilerError re) {
+        throw re;
+      } catch (RuntimeException re) {
+        throw re.getCause() == null ? re : re.getCause();
+      }
+    }
+
+    D8TestRunner withIntermediate(boolean intermediate) {
+      return withBuilderTransformation(builder -> builder.setIntermediate(intermediate));
+    }
+
+    @Override
+    D8TestRunner self() {
+      return this;
+    }
+  }
+
+  @Override
+  D8TestRunner test(String testName, String packageName, String mainClass) {
+    return new D8TestRunner(testName, packageName, mainClass);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
new file mode 100644
index 0000000..0f7386e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2017, 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;
+
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.UnaryOperator;
+import org.junit.Test;
+
+public class R8RunExamplesAndroidPTest extends RunExamplesAndroidPTest<R8Command.Builder> {
+
+  private static Map<DexVm.Version, List<String>> alsoFailsOn =
+      ImmutableMap.of(
+          DexVm.Version.V4_4_4, ImmutableList.of(
+              "invokecustom-with-shrinking"
+          ),
+          DexVm.Version.V5_1_1, ImmutableList.of(
+              "invokecustom-with-shrinking"
+          ),
+          DexVm.Version.V6_0_1, ImmutableList.of(
+              "invokecustom-with-shrinking"
+          ),
+          DexVm.Version.V7_0_0, ImmutableList.of(
+              "invokecustom-with-shrinking"
+          ),
+          DexVm.Version.DEFAULT, ImmutableList.of(
+          )
+      );
+
+  @Test
+  public void invokeCustomWithShrinking() throws Throwable {
+    test("invokecustom-with-shrinking", "invokecustom", "InvokeCustom")
+        .withMinApiLevel(AndroidApiLevel.P.getLevel())
+        .withBuilderTransformation(builder ->
+            builder.addProguardConfigurationFiles(
+                Paths.get(ToolHelper.EXAMPLES_ANDROID_P_DIR, "invokecustom/keep-rules.txt")))
+        .run();
+  }
+
+  class R8TestRunner extends TestRunner<R8TestRunner> {
+
+    R8TestRunner(String testName, String packageName, String mainClass) {
+      super(testName, packageName, mainClass);
+    }
+
+    @Override
+    R8TestRunner withMinApiLevel(int minApiLevel) {
+      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+    }
+
+    @Override
+    void build(Path inputFile, Path out) throws Throwable {
+      try {
+        R8Command.Builder builder = R8Command.builder();
+        for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
+          builder = transformation.apply(builder);
+        }
+        // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
+        builder.addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(AndroidApiLevel.O.getLevel())));
+        R8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build();
+        ToolHelper.runR8(command, this::combinedOptionConsumer);
+      } catch (ExecutionException e) {
+        throw e.getCause();
+      }
+    }
+
+    @Override
+    R8TestRunner self() {
+      return this;
+    }
+  }
+
+  @Override
+  R8TestRunner test(String testName, String packageName, String mainClass) {
+    return new R8TestRunner(testName, packageName, mainClass);
+  }
+
+  @Override
+  boolean expectedToFail(String name) {
+    return super.expectedToFail(name) || failsOn(alsoFailsOn, name);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
new file mode 100644
index 0000000..06916a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -0,0 +1,273 @@
+// Copyright (c) 2017, 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;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
+import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
+import com.android.tools.r8.utils.DexInspector.InstructionSubject;
+import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OffOrAuto;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+public abstract class RunExamplesAndroidPTest
+      <B extends BaseCommand.Builder<? extends BaseCommand, B>> {
+  static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_ANDROID_P_BUILD_DIR;
+
+  abstract class TestRunner<C extends TestRunner<C>> {
+    final String testName;
+    final String packageName;
+    final String mainClass;
+
+    Integer androidJarVersion = null;
+
+    final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
+    final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
+    final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
+
+    TestRunner(String testName, String packageName, String mainClass) {
+      this.testName = testName;
+      this.packageName = packageName;
+      this.mainClass = mainClass;
+    }
+
+    abstract C self();
+
+    C withDexCheck(Consumer<DexInspector> check) {
+      dexInspectorChecks.add(check);
+      return self();
+    }
+
+    C withClassCheck(Consumer<FoundClassSubject> check) {
+      return withDexCheck(inspector -> inspector.forAllClasses(check));
+    }
+
+    C withMethodCheck(Consumer<FoundMethodSubject> check) {
+      return withClassCheck(clazz -> clazz.forAllMethods(check));
+    }
+
+    <T extends InstructionSubject> C withInstructionCheck(
+        Predicate<InstructionSubject> filter, Consumer<T> check) {
+      return withMethodCheck(method -> {
+        if (method.isAbstract()) {
+          return;
+        }
+        Iterator<T> iterator = method.iterateInstructions(filter);
+        while (iterator.hasNext()) {
+          check.accept(iterator.next());
+        }
+      });
+    }
+
+    C withOptionConsumer(Consumer<InternalOptions> consumer) {
+      optionConsumers.add(consumer);
+      return self();
+    }
+
+    C withMainDexClass(String... classes) {
+      return withBuilderTransformation(builder -> builder.addMainDexClasses(classes));
+    }
+
+    C withInterfaceMethodDesugaring(OffOrAuto behavior) {
+      return withOptionConsumer(o -> o.interfaceMethodDesugaring = behavior);
+    }
+
+    C withTryWithResourcesDesugaring(OffOrAuto behavior) {
+      return withOptionConsumer(o -> o.tryWithResourcesDesugaring = behavior);
+    }
+
+    C withBuilderTransformation(UnaryOperator<B> builderTransformation) {
+      builderTransformations.add(builderTransformation);
+      return self();
+    }
+
+    void combinedOptionConsumer(InternalOptions options) {
+      for (Consumer<InternalOptions> consumer : optionConsumers) {
+        consumer.accept(options);
+      }
+    }
+
+    Path build() throws Throwable {
+      Path inputFile = getInputJar();
+      Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
+
+      build(inputFile, out);
+      return out;
+    }
+
+    Path getInputJar() {
+      return Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION);
+    }
+
+    void run() throws Throwable {
+      if (minSdkErrorExpected(testName)) {
+        thrown.expect(ApiLevelException.class);
+      }
+
+      String qualifiedMainClass = packageName + "." + mainClass;
+      Path inputFile = getInputJar();
+      Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
+
+      build(inputFile, out);
+
+      if (!ToolHelper.artSupported()) {
+        return;
+      }
+
+      if (!dexInspectorChecks.isEmpty()) {
+        DexInspector inspector = new DexInspector(out);
+        for (Consumer<DexInspector> check : dexInspectorChecks) {
+          check.accept(inspector);
+        }
+      }
+
+      execute(testName, qualifiedMainClass, new Path[]{inputFile}, new Path[]{out});
+    }
+
+    abstract C withMinApiLevel(int minApiLevel);
+
+    C withAndroidJar(int androidJarVersion) {
+      assert this.androidJarVersion == null;
+      this.androidJarVersion = androidJarVersion;
+      return self();
+    }
+
+    abstract void build(Path inputFile, Path out) throws Throwable;
+  }
+
+  private static List<String> minSdkErrorExpected =
+      ImmutableList.of(
+          "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk");
+
+  private static Map<DexVm.Version, List<String>> failsOn =
+      ImmutableMap.of(
+          DexVm.Version.V4_4_4, ImmutableList.of(
+              // Dex version not supported
+              "invokecustom"
+          ),
+          DexVm.Version.V5_1_1, ImmutableList.of(
+              // Dex version not supported
+              "invokecustom"
+          ),
+          DexVm.Version.V6_0_1, ImmutableList.of(
+              // Dex version not supported
+              "invokecustom"
+          ),
+          DexVm.Version.V7_0_0, ImmutableList.of(
+              // Dex version not supported
+              "invokecustom"
+          ),
+          DexVm.Version.DEFAULT, ImmutableList.of()
+      );
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  boolean failsOn(Map<DexVm.Version, List<String>> failsOn, String name) {
+    DexVm.Version vmVersion = ToolHelper.getDexVm().getVersion();
+    return failsOn.containsKey(vmVersion)
+        && failsOn.get(vmVersion).contains(name);
+  }
+
+  boolean expectedToFail(String name) {
+    return failsOn(failsOn, name);
+  }
+
+  boolean minSdkErrorExpected(String testName) {
+    return minSdkErrorExpected.contains(testName);
+  }
+
+  @Test
+  public void invokeCustom() throws Throwable {
+    test("invokecustom", "invokecustom", "InvokeCustom")
+        .withMinApiLevel(AndroidApiLevel.P.getLevel())
+        .run();
+  }
+
+  @Test
+  public void invokeCustomErrorDueToMinSdk() throws Throwable {
+    test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
+        .withMinApiLevel(AndroidApiLevel.O.getLevel())
+        .run();
+  }
+
+  abstract RunExamplesAndroidPTest<B>.TestRunner<?> test(String testName, String packageName,
+      String mainClass);
+
+  void execute(
+      String testName,
+      String qualifiedMainClass, Path[] jars, Path[] dexes)
+      throws IOException {
+
+    boolean expectedToFail = expectedToFail(testName);
+    if (expectedToFail) {
+      thrown.expect(Throwable.class);
+    }
+    String output = ToolHelper.runArtNoVerificationErrors(
+        Arrays.stream(dexes).map(path -> path.toString()).collect(Collectors.toList()),
+        qualifiedMainClass,
+        null);
+    if (!expectedToFail) {
+      ToolHelper.ProcessResult javaResult =
+          ToolHelper.runJava(
+              Arrays.stream(jars).map(path -> path.toString()).collect(Collectors.toList()),
+              qualifiedMainClass);
+      assertEquals("JVM run failed", javaResult.exitCode, 0);
+      assertTrue(
+          "JVM output does not match art output.\n\tjvm: "
+              + javaResult.stdout
+              + "\n\tart: "
+              + output.replace("\r", ""),
+          output.equals(javaResult.stdout.replace("\r", "")));
+    }
+  }
+
+  protected DexInspector getMainDexInspector(Path zip)
+      throws ZipException, IOException, ExecutionException {
+    try (ZipFile zipFile = new ZipFile(zip.toFile())) {
+      try (InputStream in =
+          zipFile.getInputStream(zipFile.getEntry(FileUtils.DEFAULT_DEX_FILENAME))) {
+        return new DexInspector(AndroidApp.fromDexProgramData(ByteStreams.toByteArray(in)));
+      }
+    }
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 79be8a8..595b753 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -59,9 +59,11 @@
   public static final String BUILD_DIR = "build/";
   public static final String EXAMPLES_DIR = "src/test/examples/";
   public static final String EXAMPLES_ANDROID_O_DIR = "src/test/examplesAndroidO/";
+  public static final String EXAMPLES_ANDROID_P_DIR = "src/test/examplesAndroidP/";
   public static final String EXAMPLES_BUILD_DIR = BUILD_DIR + "test/examples/";
   public static final String EXAMPLES_ANDROID_N_BUILD_DIR = BUILD_DIR + "test/examplesAndroidN/";
   public static final String EXAMPLES_ANDROID_O_BUILD_DIR = BUILD_DIR + "test/examplesAndroidO/";
+  public static final String EXAMPLES_ANDROID_P_BUILD_DIR = BUILD_DIR + "test/examplesAndroidP/";
   public static final String SMALI_BUILD_DIR = BUILD_DIR + "test/smali/";
 
   public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;