Add const-method-type support

Bug: 64891562
Change-Id: I889bf673cc942c0710dd9847b2074375690c96a6
diff --git a/src/main/java/com/android/tools/r8/code/BaseInstructionFactory.java b/src/main/java/com/android/tools/r8/code/BaseInstructionFactory.java
index bb31282..4e15eb9 100644
--- a/src/main/java/com/android/tools/r8/code/BaseInstructionFactory.java
+++ b/src/main/java/com/android/tools/r8/code/BaseInstructionFactory.java
@@ -456,6 +456,8 @@
         return new InvokeCustomRange(high, stream, mapping);
       case ConstMethodHandle.OPCODE:
         return new ConstMethodHandle(high, stream, mapping);
+      case ConstMethodType.OPCODE:
+        return new ConstMethodType(high, stream, mapping);
       default:
         throw new IllegalArgumentException("Illegal Opcode: 0x" + Integer.toString(opcode, 16));
     }
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
new file mode 100644
index 0000000..7e50926
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
@@ -0,0 +1,72 @@
+// 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.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.naming.ClassNameMapper;
+import java.nio.ShortBuffer;
+
+public class ConstMethodType extends Format21c {
+
+  public static final int OPCODE = 0xff;
+  public static final String NAME = "ConstMethodType";
+  public static final String SMALI_NAME = "const-method-type";
+
+  ConstMethodType(int high, BytecodeStream stream, OffsetToObjectMapping mapping) {
+    super(high, stream, mapping.getProtosMap());
+  }
+
+  public ConstMethodType(int register, DexProto methodType) {
+    super(register, methodType);
+  }
+
+  public DexProto getMethodType() {
+    return (DexProto) 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 write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+    int index = BBBB.getOffset(mapping);
+    if (index != (index & 0xffff)) {
+      throw new InternalCompilerError("MethodType-index overflow.");
+    }
+    super.write(dest, mapping);
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder) throws ApiLevelException {
+    builder.addConstMethodType(AA, (DexProto) BBBB);
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
new file mode 100644
index 0000000..3e32bb6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.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.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+
+public class ConstMethodType extends ConstInstruction {
+
+  private final DexProto methodType;
+
+  public ConstMethodType(Value dest, DexProto methodType) {
+    super(dest);
+    dest.markNeverNull();
+    this.methodType = methodType;
+  }
+
+  public Value dest() {
+    return outValue;
+  }
+
+  public DexProto getValue() {
+    return methodType;
+  }
+
+  @Override
+  public void buildDex(DexBuilder builder) {
+    int dest = builder.allocatedRegister(dest(), getNumber());
+    builder.add(this, new com.android.tools.r8.code.ConstMethodType(dest, methodType));
+  }
+
+  @Override
+  public boolean identicalNonValueParts(Instruction other) {
+    return other.asConstMethodType().methodType == methodType;
+  }
+
+  @Override
+  public int compareNonValueParts(Instruction other) {
+    return methodType.slowCompareTo(other.asConstMethodType().methodType);
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    assert false : "ConstMethodType has no register arguments.";
+    return 0;
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    return Constants.U8BIT_MAX;
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + " \"" + methodType + "\"";
+  }
+
+  @Override
+  public boolean instructionTypeCanThrow() {
+    return true;
+  }
+
+  @Override
+  public boolean isOutConstant() {
+    return true;
+  }
+
+  @Override
+  public boolean isConstString() {
+    return true;
+  }
+
+  @Override
+  public ConstMethodType asConstMethodType() {
+    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 24c3925..5fd2ff1 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
@@ -458,6 +458,14 @@
     return null;
   }
 
+  public boolean isConstMethodType() {
+    return false;
+  }
+
+  public ConstMethodType asConstMethodType() {
+    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 c84adcb..70a9804 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
@@ -33,6 +33,7 @@
 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.ConstMethodType;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.ConstType;
@@ -813,6 +814,19 @@
     add(instruction);
   }
 
+  public void addConstMethodType(int dest, DexProto methodType)
+      throws ApiLevelException {
+    if (!options.canUseConstantMethodType()) {
+      throw new ApiLevelException(
+          AndroidApiLevel.P,
+          "Const-method-type",
+          null /* sourceString */);
+    }
+    Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.CAN_THROW);
+    ConstMethodType instruction = new ConstMethodType(out, methodType);
+    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 a4203a4..9f3d3a4 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
@@ -143,6 +143,7 @@
   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");
+  static final Type METHOD_TYPE_TYPE = Type.getObjectType("java/lang/invoke/MethodType");
 
   private static final int[] NO_TARGETS = {};
 
@@ -2730,8 +2731,13 @@
   private void build(LdcInsnNode insn, IRBuilder builder) throws ApiLevelException {
     if (insn.cst instanceof Type) {
       Type type = (Type) insn.cst;
-      int dest = state.push(type);
-      builder.addConstClass(dest, application.getTypeFromDescriptor(type.getDescriptor()));
+      if (type.getSort() == Type.METHOD) {
+        int dest = state.push(METHOD_TYPE_TYPE);
+        builder.addConstMethodType(dest, application.getProto(type.getDescriptor()));
+      } else {
+        int dest = state.push(type);
+        builder.addConstClass(dest, application.getTypeFromDescriptor(type.getDescriptor()));
+      }
     } else if (insn.cst instanceof String) {
       int dest = state.push(STRING_TYPE);
       builder.addConstString(dest, application.getString((String) insn.cst));
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index f11af49..6faba19 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -122,7 +122,7 @@
 
       // Catch all matching.
       if (other == REFERENCE_TYPE) {
-        return sort == Type.OBJECT || sort == Type.ARRAY;
+        return sort == Type.OBJECT || sort == Type.ARRAY || sort == Type.METHOD;
       }
       if (other == OBJECT_TYPE) {
         return sort == Type.OBJECT;
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 e1599b9..46bcbd5 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -48,7 +48,11 @@
   @Override
   public void visitLdcInsn(Object cst) {
     if (cst instanceof Type) {
-      registry.registerConstClass(application.getType((Type) cst));
+      // Nothing to register for method type, it represents only a prototype not associated with a
+      // method name.
+      if (((Type) cst).getSort() != Type.METHOD) {
+        registry.registerConstClass(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 9f883bf..7f6fe45 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -354,6 +354,10 @@
     return minApiLevel >= AndroidApiLevel.P.getLevel();
   }
 
+  public boolean canUseConstantMethodType() {
+    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
index 282b5ae..e8447a3 100644
--- a/src/test/examplesAndroidP/invokecustom/InvokeCustom.java
+++ b/src/test/examplesAndroidP/invokecustom/InvokeCustom.java
@@ -24,11 +24,15 @@
     System.out.println(mhGetStatic.invoke());
   }
 
+  private static void targetMethodTest3(MethodType mt)
+      throws Throwable {
+    System.out.println("MethodType: " + mt.toString());
+  }
+
   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
index b9a5126..375dc34 100644
--- a/src/test/examplesAndroidP/invokecustom/TestGenerator.java
+++ b/src/test/examplesAndroidP/invokecustom/TestGenerator.java
@@ -43,6 +43,7 @@
           @Override
           public void visitEnd() {
             generateMethodTest1(cw);
+            generateMethodTest2(cw);
             generateMethodMain(cw);
             super.visitEnd();
           }
@@ -56,6 +57,8 @@
             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.visitMethodInsn(
+        Opcodes.INVOKESTATIC, Type.getInternalName(InvokeCustom.class), "test2", "()V", false);
     mv.visitInsn(Opcodes.RETURN);
     mv.visitMaxs(-1, -1);
   }
@@ -81,4 +84,22 @@
     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 generateMethodTest2(ClassVisitor cv) {
+    MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test2", "()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(Type.getMethodType("(ZBSCIFJDLjava/lang/String;)Ljava/lang/Object;"));
+    mv.visitInvokeDynamicInsn("targetMethodTest3", "(Ljava/lang/invoke/MethodType;)V",
+        bootstrap);
+    mv.visitInsn(Opcodes.RETURN);
+    mv.visitMaxs(-1, -1);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 171cbd2..1fb3175 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -976,9 +976,6 @@
   );
 
   private static List<String> failuresToTriage = ImmutableList.of(
-      // const-method-handle and const-method-type
-      "979-const-method-handle",
-
       // Dex file input into a jar file, not yet supported by the test framework.
       "663-odd-dex-size",
       "663-odd-dex-size2",
@@ -1667,6 +1664,7 @@
           specification.directory.listFiles((File file) ->
               file.getName().endsWith(".dex") && !file.getName().startsWith("jasmin"));
     }
+
     List<String> fileNames = new ArrayList<>();
     for (File file : inputFiles) {
       fileNames.add(file.getCanonicalPath());
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d0cc56a..b2f2a58 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -428,6 +428,10 @@
   }
 
   public static String getAndroidJar(int minSdkVersion) {
+    if (minSdkVersion == AndroidApiLevel.P.getLevel()) {
+      // TODO(mikaelpeltier) Android P does not yet have his android.jar use the O version
+      minSdkVersion = AndroidApiLevel.O.getLevel();
+    }
     return String.format(
         ANDROID_JAR_PATTERN,
         minSdkVersion == AndroidApiLevel.getDefault().getLevel() ? DEFAULT_MIN_SDK : minSdkVersion);