Merge "CF backend: Implement InvokePolymorphic for VarHandle"
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))