CF backend: Implement InvokePolymorphic for VarHandle

Also add R8CFRunExamplesJava9Test to run the four Java 9 tests on the
CF backend; the "varhandle" test covers this CL.

Also disallow setMinApiLevel() in R8 when outputting class files.

Also change an Unreachable exception message in JarSourceCode based on
today's JVM9 spec study with Ian.

Change-Id: Iad81968a000e2348d973d5b688039ad2ffaec3e4
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 42982d5..9a260ea 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -103,7 +103,7 @@
     private OutputMode outputMode = OutputMode.DexIndexed;
 
     private CompilationMode mode;
-    private int minApiLevel = AndroidApiLevel.getDefault().getLevel();
+    private int minApiLevel = 0;
     private boolean disableDesugaring = false;
 
     Builder() {}
@@ -228,13 +228,20 @@
 
     /** Get the minimum API level (aka SDK version). */
     public int getMinApiLevel() {
-      return minApiLevel;
+      return isMinApiLevelSet() ? minApiLevel : AndroidApiLevel.getDefault().getLevel();
+    }
+
+    boolean isMinApiLevelSet() {
+      return minApiLevel != 0;
     }
 
     /** Set the minimum required API level (aka SDK version). */
     public B setMinApiLevel(int minApiLevel) {
-      assert minApiLevel > 0;
-      this.minApiLevel = minApiLevel;
+      if (minApiLevel <= 0) {
+        getReporter().error("Invalid minApiLevel: " + minApiLevel);
+      } else {
+        this.minApiLevel = minApiLevel;
+      }
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4615fa5..5f27356 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -256,6 +256,9 @@
               "R8 does not support compiling DEX inputs", new PathOrigin(file)));
         }
       }
+      if (getProgramConsumer() instanceof ClassFileConsumer && isMinApiLevelSet()) {
+        reporter.error("R8 does not support --min-api when compiling to class files");
+      }
       super.validate();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 718d23c..01cd5f5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokePolymorphicRange;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -14,7 +13,6 @@
 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.conversion.JarSourceCode;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -87,15 +85,12 @@
   public void buildCf(CfBuilder builder) {
     DexMethod dexMethod = getInvokedMethod();
     DexItemFactory factory = builder.getFactory();
-
-    if (dexMethod.holder.getInternalName().equals(JarSourceCode.INTERNAL_NAME_METHOD_HANDLE)) {
-      DexMethod method = factory.createMethod(dexMethod.holder, getProto(), dexMethod.name);
-      builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method));
-    } else {
-      assert dexMethod.holder.getInternalName().equals(JarSourceCode.INTERNAL_NAME_VAR_HANDLE);
-      // VarHandle is new in Java 9
-      throw new Unimplemented();
-    }
+    // When we translate InvokeVirtual on MethodHandle/VarHandle into InvokePolymorphic,
+    // we translate the invoked prototype into a generic prototype that simply accepts Object[].
+    // To translate InvokePolymorphic back into InvokeVirtual, use the original prototype
+    // that is stored in getProto().
+    DexMethod method = factory.createMethod(dexMethod.holder, getProto(), dexMethod.name);
+    builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method));
   }
 
   @Override
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 26a94d2..b584448 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
@@ -2658,8 +2658,8 @@
     Handle bsmHandle = insn.bsm;
     if (bsmHandle.getTag() != Opcodes.H_INVOKESTATIC &&
         bsmHandle.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
-      throw new Unreachable(
-          "Bootstrap handle is not yet supported: tag == " + bsmHandle.getTag());
+      // JVM9 §4.7.23 note: Tag must be InvokeStatic or NewInvokeSpecial.
+      throw new Unreachable("Bootstrap handle invalid: tag == " + bsmHandle.getTag());
     }
     // Resolve the bootstrap method.
     DexMethodHandle bootstrapMethod = getMethodHandle(application, bsmHandle);
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 1073cf9..cc8c9ce 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -401,50 +401,54 @@
   }
 
   public boolean canUseInvokePolymorphicOnVarHandle() {
-    return minApiLevel >= AndroidApiLevel.P.getLevel();
+    return hasMinApi(AndroidApiLevel.P);
   }
 
   public boolean canUseInvokePolymorphic() {
-    return minApiLevel >= AndroidApiLevel.O.getLevel();
+    return hasMinApi(AndroidApiLevel.O);
   }
 
   public boolean canUseConstantMethodHandle() {
-    return minApiLevel >= AndroidApiLevel.P.getLevel();
+    return hasMinApi(AndroidApiLevel.P);
+  }
+
+  private boolean hasMinApi(AndroidApiLevel level) {
+    return isGeneratingClassFiles() || minApiLevel >= level.getLevel();
   }
 
   public boolean canUseConstantMethodType() {
-    return minApiLevel >= AndroidApiLevel.P.getLevel();
+    return hasMinApi(AndroidApiLevel.P);
   }
 
   public boolean canUseInvokeCustom() {
-    return minApiLevel >= AndroidApiLevel.O.getLevel();
+    return hasMinApi(AndroidApiLevel.O);
   }
 
   public boolean canUseDefaultAndStaticInterfaceMethods() {
-    return minApiLevel >= AndroidApiLevel.N.getLevel();
+    return hasMinApi(AndroidApiLevel.N);
   }
 
   public boolean canUsePrivateInterfaceMethods() {
-    return minApiLevel >= AndroidApiLevel.N.getLevel();
+    return hasMinApi(AndroidApiLevel.N);
   }
 
   public boolean canUseMultidex() {
-    return intermediate || minApiLevel >= AndroidApiLevel.L.getLevel();
+    return intermediate || hasMinApi(AndroidApiLevel.L);
   }
 
   public boolean canUseLongCompareAndObjectsNonNull() {
-    return minApiLevel >= AndroidApiLevel.K.getLevel();
+    return hasMinApi(AndroidApiLevel.K);
   }
 
   public boolean canUseSuppressedExceptions() {
-    return minApiLevel >= AndroidApiLevel.K.getLevel();
+    return hasMinApi(AndroidApiLevel.K);
   }
 
   // APIs for accessing parameter names annotations are not available before Android O, thus does
   // not emit them to avoid wasting space in Dex files because runtimes before Android O will ignore
   // them.
   public boolean canUseParameterNameAnnotations() {
-    return minApiLevel >= AndroidApiLevel.O.getLevel();
+    return hasMinApi(AndroidApiLevel.O);
   }
 
   // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
@@ -456,7 +460,7 @@
   //
   // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
   public boolean canUseFilledNewArrayOfObjects() {
-    return minApiLevel >= AndroidApiLevel.K.getLevel();
+    return hasMinApi(AndroidApiLevel.K);
   }
 
   // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
@@ -492,7 +496,7 @@
   // we can only use not instructions if we are targeting Art-based
   // phones.
   public boolean canUseNotInstruction() {
-    return minApiLevel >= AndroidApiLevel.L.getLevel();
+    return hasMinApi(AndroidApiLevel.L);
   }
 
   // Art before M has a verifier bug where the type of the contents of the receiver register is
diff --git a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
new file mode 100644
index 0000000..f4d3da5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
@@ -0,0 +1,127 @@
+// 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;
+
+import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.UnaryOperator;
+
+public class R8CFRunExamplesJava9Test extends RunExamplesJava9Test<R8Command.Builder> {
+
+  class R8CFTestRunner extends TestRunner<R8CFTestRunner> {
+
+    R8CFTestRunner(String testName, String packageName, String mainClass) {
+      super(testName, packageName, mainClass);
+    }
+
+    @Override
+    R8CFTestRunner withMinApiLevel(int minApiLevel) {
+      return self();
+    }
+
+    @Override
+    void build(Path inputFile, Path out) throws Throwable {
+      R8Command.Builder builder = R8Command.builder();
+      for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
+        builder = transformation.apply(builder);
+      }
+      builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
+      R8Command command =
+          builder.addProgramFiles(inputFile).setOutput(out, OutputMode.ClassFile).build();
+      ToolHelper.runR8(command, this::combinedOptionConsumer);
+    }
+
+    @Override
+    void run() throws Throwable {
+      boolean expectedToThrow = minSdkErrorExpectedCf(testName);
+      if (expectedToThrow) {
+        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.isJava9Runtime()) {
+        System.out.println("No Java 9 support; skip execution tests");
+        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});
+
+      if (expectedToThrow) {
+        System.out.println("Did not throw ApiLevelException as expected");
+      }
+    }
+
+    @Override
+    R8CFTestRunner self() {
+      return this;
+    }
+  }
+
+  @Override
+  R8CFTestRunner test(String testName, String packageName, String mainClass) {
+    return new R8CFTestRunner(testName, packageName, mainClass);
+  }
+
+  void execute(String testName, String qualifiedMainClass, Path[] inputJars, Path[] outputJars)
+      throws IOException {
+    boolean expectedToFail = expectedToFailCf(testName);
+    if (expectedToFail) {
+      thrown.expect(Throwable.class);
+    }
+    ProcessResult outputResult = ToolHelper.runJava(Arrays.asList(outputJars), qualifiedMainClass);
+    ToolHelper.ProcessResult inputResult =
+        ToolHelper.runJava(ImmutableList.copyOf(inputJars), qualifiedMainClass);
+    assertEquals(inputResult.toString(), outputResult.toString());
+    if (inputResult.exitCode != 0) {
+      System.out.println(inputResult);
+    }
+    assertEquals(0, inputResult.exitCode);
+    if (expectedToFail) {
+      System.out.println("Did not fail as expected");
+    }
+  }
+
+  private static List<String> expectedFailures =
+      ImmutableList.of(
+          "native-private-interface-methods",
+          "desugared-private-interface-methods"
+      );
+
+  private boolean expectedToFailCf(String testName) {
+    System.out.println(testName + " " + expectedFailures.contains(testName));
+    return expectedFailures.contains(testName);
+  }
+
+  private static List<String> minSdkErrorExpected =
+      ImmutableList.of(
+      );
+
+  private boolean minSdkErrorExpectedCf(String testName) {
+    System.out.println(testName);
+    return minSdkErrorExpected.contains(testName);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index ebb8100..4d7240e 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1488,7 +1488,6 @@
           AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name);
           if (minSdkVersion != null) {
             builder.setMinApiLevel(minSdkVersion.getLevel());
-            r8builder.setMinApiLevel(minSdkVersion.getLevel());
             builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
             r8builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
           } else {
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index ba25396..d92ff90 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -85,10 +85,12 @@
     AndroidApiLevel apiLevel = AndroidApiLevel.P;
     Builder cfBuilder =
         R8Command.builder()
-            .setMinApiLevel(apiLevel.getLevel())
             .setMode(CompilationMode.DEBUG)
             .addLibraryFiles(ToolHelper.getAndroidJar(apiLevel))
             .setProgramConsumer(programConsumer);
+    if (!(programConsumer instanceof ClassFileConsumer)) {
+      cfBuilder.setMinApiLevel(apiLevel.getLevel());
+    }
     for (Class<?> c : inputClasses) {
       byte[] classAsBytes = getClassAsBytes(c);
       cfBuilder.addClassProgramData(classAsBytes, Origin.unknown());
diff --git a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
index 4916e1d..e4a88f0 100644
--- a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
@@ -25,7 +25,6 @@
       AndroidAppConsumers sink = new AndroidAppConsumers();
       R8.run(
           R8Command.builder()
-              .setMinApiLevel(minApi.getLevel())
               .setMode(CompilationMode.DEBUG)
               .addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
               .setProgramConsumer(sink.wrapClassFileConsumer(null))