Add tests for virtual dispatch argument propagation Change-Id: Ib74eda42dd1318d4ba3b86ebe2827e628a89d90f
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationToClassesOutsideLowerBoundTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationToClassesOutsideLowerBoundTest.java new file mode 100644 index 0000000..1e2140c --- /dev/null +++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationToClassesOutsideLowerBoundTest.java
@@ -0,0 +1,130 @@ +// Copyright (c) 2021, 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.optimize.argumentpropagation; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static junit.framework.TestCase.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ArgumentPropagationToClassesOutsideLowerBoundTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addHorizontallyMergedClassesInspector( + HorizontallyMergedClassesInspector::assertNoClassesMerged) + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + MethodSubject aMethodSubject = inspector.clazz(A.class).uniqueMethodWithName("m"); + assertThat(aMethodSubject, isPresent()); + assertTrue( + aMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("A: Not null"))); + assertTrue( + aMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("A: Null"))); + + // TODO(b/190154391): B.m() is always called with non-null. + MethodSubject bMethodSubject = inspector.clazz(B.class).uniqueMethodWithName("m"); + assertThat(bMethodSubject, isPresent()); + assertTrue( + bMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("B: Not null"))); + assertTrue( + bMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("B: Null"))); + + // TODO(b/190154391): C.m() is always called with null. + MethodSubject cMethodSubject = inspector.clazz(C.class).uniqueMethodWithName("m"); + assertThat(cMethodSubject, isPresent()); + assertTrue( + cMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("C: Not null"))); + assertTrue( + cMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("C: Null"))); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A: Not null", "A: Null"); + } + + static class Main { + + public static void main(String[] args) { + A aOrB = System.currentTimeMillis() > 0 ? new A() : new B(); + aOrB.m(new Object()); + + A aOrC = System.currentTimeMillis() > 0 ? new A() : new C(); + aOrC.m(null); + } + } + + static class A { + + void m(Object o) { + if (o != null) { + System.out.println("A: Not null"); + } else { + System.out.println("A: Null"); + } + } + } + + @NoHorizontalClassMerging + static class B extends A { + + void m(Object o) { + if (o != null) { + System.out.println("B: Not null"); + } else { + System.out.println("B: Null"); + } + } + } + + @NoHorizontalClassMerging + static class C extends A { + + void m(Object o) { + if (o != null) { + System.out.println("C: Not null"); + } else { + System.out.println("C: Null"); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToUnrelatedMethodTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToUnrelatedMethodTest.java new file mode 100644 index 0000000..03d63d9 --- /dev/null +++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToUnrelatedMethodTest.java
@@ -0,0 +1,147 @@ +// Copyright (c) 2021, 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.optimize.argumentpropagation; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static junit.framework.TestCase.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NoHorizontalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class UpwardsInterfacePropagationToUnrelatedMethodTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection parameters() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .addOptionsModification( + options -> + options + .callSiteOptimizationOptions() + .setEnableExperimentalArgumentPropagation(true)) + .addHorizontallyMergedClassesInspector( + HorizontallyMergedClassesInspector::assertNoClassesMerged) + .addVerticallyMergedClassesInspector( + VerticallyMergedClassesInspector::assertNoClassesMerged) + .enableNoHorizontalClassMergingAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect( + inspector -> { + MethodSubject aMethodSubject = inspector.clazz(A.class).uniqueMethodWithName("m"); + assertThat(aMethodSubject, isPresent()); + assertTrue( + aMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("A: Not null"))); + assertTrue( + aMethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("A: Null"))); + + MethodSubject b2MethodSubject = inspector.clazz(B2.class).uniqueMethodWithName("m"); + assertThat(b2MethodSubject, isPresent()); + assertTrue( + b2MethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("B2: Not null"))); + assertTrue( + b2MethodSubject + .streamInstructions() + .noneMatch(instruction -> instruction.isConstString("B2: Null"))); + + MethodSubject b3MethodSubject = inspector.clazz(B3.class).uniqueMethodWithName("m"); + assertThat(b3MethodSubject, isPresent()); + assertTrue( + b3MethodSubject + .streamInstructions() + .noneMatch(instruction -> instruction.isConstString("B3: Not null"))); + assertTrue( + b3MethodSubject + .streamInstructions() + .anyMatch(instruction -> instruction.isConstString("B3: Null"))); + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A: Not null", "A: Null"); + } + + static class Main { + + public static void main(String[] args) { + // Call A.m() or B2.m() with a non-null object. + A a = System.currentTimeMillis() >= 0 ? new B() : new B2(); + a.m(new Object()); + + // Call A.m() or B3.m() with a null object. + I i = System.currentTimeMillis() >= 0 ? new B() : new B3(); + i.m(null); + } + } + + interface I { + + void m(Object o); + } + + static class A { + + public void m(Object o) { + if (o != null) { + System.out.println("A: Not null"); + } else { + System.out.println("A: Null"); + } + } + } + + @NoHorizontalClassMerging + static class B extends A implements I {} + + @NoHorizontalClassMerging + static class B2 extends A { + + @Override + public void m(Object o) { + if (o != null) { + System.out.println("B2: Not null"); + } else { + System.out.println("B2: Null"); + } + } + } + + static class B3 implements I { + + @Override + public void m(Object o) { + if (o != null) { + System.out.println("B3: Not null"); + } else { + System.out.println("B3: Null"); + } + } + } +}