Add test for dynamic type optimization
Bug: 127461806
Change-Id: I02f38db6fe358234e0d838e1112a7a6703d74f65
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 3291692..f0591ee 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -256,6 +256,7 @@
internal.passthroughDexCode = true;
// Assert some of R8 optimizations are disabled.
+ assert !internal.enableDynamicTypeOptimization;
assert !internal.enableInlining;
assert !internal.enableClassInlining;
assert !internal.enableHorizontalClassMerging;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 5980ac3..f000ece 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -676,6 +676,7 @@
? LineNumberOptimization.ON
: LineNumberOptimization.OFF;
+ assert internal.enableDynamicTypeOptimization || !proguardConfiguration.isOptimizing();
assert internal.enableHorizontalClassMerging || !proguardConfiguration.isOptimizing();
assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
if (internal.debug) {
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 3a5bd94..15ee862 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -112,6 +112,7 @@
public void disableGlobalOptimizations() {
enableArgumentRemoval = false;
+ enableDynamicTypeOptimization = false;
enableInlining = false;
enableClassInlining = false;
enableClassStaticizer = false;
@@ -133,6 +134,7 @@
public boolean passthroughDexCode = false;
// Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations.
+ public boolean enableDynamicTypeOptimization = true;
public boolean enableHorizontalClassMerging = true;
public boolean enableVerticalClassMerging = true;
public boolean enableArgumentRemoval = true;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
new file mode 100644
index 0000000..849927a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
@@ -0,0 +1,199 @@
+// Copyright (c) 2019, 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.optimize.dynamictype;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DynamicTypeOptimizationTest extends TestBase {
+
+ private final boolean enableDynamicTypeOptimization;
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{1}, enable dynamic type optimization: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ }
+
+ public DynamicTypeOptimizationTest(
+ boolean enableDynamicTypeOptimization, TestParameters parameters) {
+ this.enableDynamicTypeOptimization = enableDynamicTypeOptimization;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(DynamicTypeOptimizationTest.class)
+ .addKeepMainRule(TestClass.class)
+ // Keep B to ensure that we will treat it as being instantiated.
+ .addKeepClassRulesWithAllowObfuscation(B.class)
+ .addOptionsModification(
+ options -> options.enableDynamicTypeOptimization = enableDynamicTypeOptimization)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(StringUtils.lines("Hello world!"));
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject mainClassSubject = inspector.clazz(TestClass.class);
+ assertThat(mainClassSubject, isPresent());
+
+ ClassSubject interfaceSubject = inspector.clazz(I.class);
+ assertThat(interfaceSubject, isPresent());
+
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+
+ // Verify that the check-cast instruction is still present in testCheckCastRemoval().
+ MethodSubject testCheckCastRemovalMethod =
+ mainClassSubject.uniqueMethodWithName("testCheckCastRemoval");
+ assertThat(testCheckCastRemovalMethod, isPresent());
+ assertTrue(
+ testCheckCastRemovalMethod
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isCheckCast(aClassSubject.getFinalName())));
+
+ // Verify that the instance-of instruction is only present in testInstanceOfRemoval() if the
+ // dynamic type optimization is disabled.
+ MethodSubject testInstanceOfRemovalMethod =
+ mainClassSubject.uniqueMethodWithName("testInstanceOfRemoval");
+ assertThat(testInstanceOfRemovalMethod, isPresent());
+ // TODO(b/127461806): Update expectation.
+ assertTrue(
+ testInstanceOfRemovalMethod
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isInstanceOf(aClassSubject.getFinalName())));
+
+ // Verify that world() has been inlined() into testMethodInlining() unless the dynamic type
+ // optimization is disabled.
+ MethodSubject testMethodInliningMethod =
+ mainClassSubject.uniqueMethodWithName("testMethodInlining");
+ assertThat(testMethodInliningMethod, isPresent());
+ // TODO(b/127461806): Update expectation.
+ assertThat(
+ testMethodInliningMethod, invokesMethod(interfaceSubject.uniqueMethodWithName("world")));
+
+ // Verify that exclamationMark() has been rebound in testMethodRebinding() unless the dynamic
+ // type optimization is disabled.
+ MethodSubject testMethodRebindingMethod =
+ mainClassSubject.uniqueMethodWithName("testMethodRebinding");
+ assertThat(testMethodRebindingMethod, isPresent());
+ // TODO(b/127461806): Update expectation.
+ assertThat(
+ testMethodRebindingMethod,
+ invokesMethod(interfaceSubject.uniqueMethodWithName("exclamationMark")));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ testCheckCastRemoval();
+ testInstanceOfRemoval();
+ testMethodInlining();
+ testMethodRebinding();
+ }
+
+ @NeverInline
+ private static void testCheckCastRemoval() {
+ // Used to verify that we do not remove check-cast instructions, since these are sometimes
+ // required for the program to pass the static type checker.
+ A obj = (A) get();
+ obj.hello();
+ }
+
+ @NeverInline
+ private static void testInstanceOfRemoval() {
+ // Used to verify that we remove trivial instance-of instructions.
+ if (get() instanceof A) {
+ System.out.print(" ");
+ }
+ }
+
+ @NeverInline
+ private static void testMethodInlining() {
+ // Used to verify that we identify a single target despite of the imprecise static type.
+ get().world();
+ }
+
+ @NeverInline
+ private static void testMethodRebinding() {
+ // Used to verify that we rebind to the most specific target when inlining is not possible.
+ get().exclamationMark();
+ }
+
+ @NeverInline
+ private static I get() {
+ return new A();
+ }
+ }
+
+ interface I {
+
+ void hello();
+
+ void world();
+
+ @NeverInline
+ void exclamationMark();
+ }
+
+ static class A implements I {
+
+ @Override
+ public void hello() {
+ System.out.print("Hello");
+ }
+
+ @Override
+ public void world() {
+ System.out.print("world");
+ }
+
+ @NeverInline
+ @Override
+ public void exclamationMark() {
+ System.out.println("!");
+ }
+ }
+
+ static class B implements I {
+
+ @Override
+ public void hello() {
+ System.out.println("Unreachable");
+ }
+
+ @Override
+ public void world() {
+ System.out.println("Unreachable");
+ }
+
+ @NeverInline
+ @Override
+ public void exclamationMark() {
+ System.out.println("Unreachable");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 5f1b405..ded4f14 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -222,6 +222,11 @@
}
@Override
+ public boolean isInstanceOf(String type) {
+ return isInstanceOf() && ((CfInstanceOf) instruction).getType().toString().equals(type);
+ }
+
+ @Override
public boolean isIf() {
return instruction instanceof CfIf || instruction instanceof CfIfCmp;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 7d5381a..5dacc5a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -31,7 +31,7 @@
@Override
public void describeTo(Description description) {
- description.appendText(" does not invoke `" + target.toSourceString() + "`");
+ description.appendText("invokes method `" + target.toSourceString() + "`");
}
@Override
@@ -42,6 +42,6 @@
}
public static Predicate<InstructionSubject> isInvokeWithTarget(DexMethod target) {
- return instruction -> instruction.isInvokeStatic() && instruction.getMethod() == target;
+ return instruction -> instruction.isInvoke() && instruction.getMethod() == target;
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 93faa64..3b04382 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -340,6 +340,11 @@
return instruction instanceof InstanceOf;
}
+ @Override
+ public boolean isInstanceOf(String type) {
+ return isInstanceOf() && ((InstanceOf) instruction).getType().toString().equals(type);
+ }
+
public boolean isConst4() {
return instruction instanceof Const4;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index f2eb753..d297a3b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -78,6 +78,8 @@
boolean isInstanceOf();
+ boolean isInstanceOf(String type);
+
boolean isIf(); // Also include CF/if_cmp* instructions.
boolean isPackedSwitch();