Reland "Add test for companion classes"
This reverts commit 19070163091c90b6b89136da0d5dd945fa8edc66.
Change-Id: I5c5e8298146a695dd091c84671a18a78a98fc3b0
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
index e4235e6..d1731e9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
@@ -8,12 +8,14 @@
import static com.google.common.base.Predicates.or;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.DexItemBasedConstString;
+import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.Phi;
@@ -33,11 +35,13 @@
public class BasicBlockBehavioralSubsumption {
private final AppView<?> appView;
+ private final IRCode code;
private final ProgramMethod context;
- public BasicBlockBehavioralSubsumption(AppView<?> appView, ProgramMethod context) {
+ public BasicBlockBehavioralSubsumption(AppView<?> appView, IRCode code) {
this.appView = appView;
- this.context = context;
+ this.code = code;
+ this.context = code.context();
}
public boolean isSubsumedBy(BasicBlock block, BasicBlock other) {
@@ -148,7 +152,25 @@
}
private boolean isBlockLocalInstructionWithoutSideEffects(Instruction instruction) {
- return definesBlockLocalValue(instruction) && !instructionMayHaveSideEffects(instruction);
+ if (!definesBlockLocalValue(instruction)) {
+ return false;
+ }
+ if (instruction.instructionMayHaveSideEffects(appView, context)) {
+ return false;
+ }
+ // For constructor calls include field initialization side effects.
+ if (instruction.isInvokeConstructor(appView.dexItemFactory())) {
+ DexEncodedMethod singleTarget =
+ instruction.asInvokeDirect().lookupSingleTarget(appView, context);
+ if (singleTarget == null) {
+ assert false;
+ return false;
+ }
+ if (singleTarget.getOptimizationInfo().mayHaveSideEffects()) {
+ return false;
+ }
+ }
+ return true;
}
private boolean definesBlockLocalValue(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 70efbeb..4c61a1c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1129,10 +1129,9 @@
BasicBlock defaultTarget = theSwitch.fallthroughBlock();
SwitchCaseEliminator eliminator = null;
BasicBlockBehavioralSubsumption behavioralSubsumption =
- new BasicBlockBehavioralSubsumption(appView, code.context());
+ new BasicBlockBehavioralSubsumption(appView, code);
// Compute the set of switch cases that can be removed.
- int alwaysHitCase = -1;
for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
BasicBlock targetBlock = theSwitch.targetBlock(i);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 71684b1..26aa681 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -255,12 +255,21 @@
}
@Override
+ public boolean isDeadIfInValueIsDead() {
+ return true;
+ }
+
+ @Override
public Iterable<Value> getValuesRequiredToBeDead() {
return () -> Iterators.singletonIterator(inValueRequiredToBeDead);
}
};
}
+ public boolean isDeadIfInValueIsDead() {
+ return false;
+ }
+
public boolean isDeadIfOutValueIsDead() {
return false;
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 4149659..37f8a22 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -126,6 +126,15 @@
dexItemFactory, horizontallyMergedClasses))));
}
+ public T addHorizontallyMergedClassesInspectorIf(
+ boolean condition, Consumer<HorizontallyMergedClassesInspector> inspector) {
+
+ if (condition) {
+ return addHorizontallyMergedClassesInspector(inspector);
+ }
+ return self();
+ }
+
public T addHorizontallyMergedLambdaClassesInspector(
Consumer<HorizontallyMergedLambdaClassesInspector> inspector) {
return addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompanionClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompanionClassMergingTest.java
new file mode 100644
index 0000000..b14763f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompanionClassMergingTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2020, 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.A;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.B;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.Main;
+import org.junit.Test;
+
+public class CompanionClassMergingTest extends HorizontalClassMergingTestBase {
+ public CompanionClassMergingTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .addOptionsModification(options -> options.enableClassInlining = false)
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging,
+ inspector -> inspector.assertMergedInto(B.Companion.class, A.Companion.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("foo a 0", "foo b 1")
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(B.class), isPresent());
+
+ assertThat(codeInspector.clazz(A.Companion.class), isPresent());
+ assertThat(
+ codeInspector.clazz(B.Companion.class),
+ notIf(isPresent(), enableHorizontalClassMerging));
+ });
+ }
+
+ @NeverClassInline
+ public static class A {
+ public static final Companion companion = new Companion(null);
+ public static String foo;
+
+ static {
+ foo = "foo a " + Main.next();
+ }
+
+ public static String access$getFoo$cp() {
+ return foo;
+ }
+
+ public static class Companion {
+ public Companion() {}
+
+ public Companion(Object obj) {
+ this();
+ }
+
+ public String getFoo() {
+ return access$getFoo$cp();
+ }
+ }
+ }
+
+ @NeverClassInline
+ public static class B {
+ public static final Companion companion = new Companion(null);
+ public static String foo;
+
+ static {
+ foo = "foo b " + Main.next();
+ }
+
+ public static String access$getFoo$cp() {
+ return foo;
+ }
+
+ public static class Companion {
+ public Companion() {}
+
+ public Companion(Object obj) {
+ this();
+ }
+
+ public String getFoo() {
+ return access$getFoo$cp();
+ }
+ }
+ }
+
+ public static class Main {
+ static int COUNT = 0;
+
+ public static int next() {
+ return COUNT++;
+ }
+
+ public static void main(String[] args) {
+ System.out.println(A.companion.getFoo());
+ System.out.println(B.companion.getFoo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
index ffdae33..4cdca16 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
@@ -5,11 +5,13 @@
package com.android.tools.r8.classmerging.horizontal;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.CompanionClassMergingTest.A;
+import com.android.tools.r8.classmerging.horizontal.CompanionClassMergingTest.B;
import org.junit.Test;
public class EmptyClassTest extends HorizontalClassMergingTestBase {
@@ -26,36 +28,36 @@
options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
.enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
.run(parameters.getRuntime(), Main.class)
- .assertSuccess()
+ .assertSuccessWithOutputLines("a", "b: foo")
.inspect(
codeInspector -> {
- if (enableHorizontalClassMerging) {
- assertThat(codeInspector.clazz(A.class), isPresent());
- assertThat(codeInspector.clazz(B.class), not(isPresent()));
- // TODO(b/165517236): Explicitly check classes have been merged.
- } else {
- assertThat(codeInspector.clazz(A.class), isPresent());
- assertThat(codeInspector.clazz(B.class), isPresent());
- }
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(
+ codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
});
}
@NeverClassInline
- public static class A {}
+ public static class A {
+ public A() {
+ System.out.println("a");
+ }
+ }
@NeverClassInline
public static class B {
- // TODO(b/164924717): remove non overlapping constructor requirement
- public B(String s) {}
+ public B(String s) {
+ System.out.println("b: " + s);
+ }
}
public static class Main {
public static void main(String[] args) {
A a = new A();
- System.out.println(a);
- B b = new B("");
- System.out.println(b);
+ B b = new B("foo");
}
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
index 27b1a0c..86e30dc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
@@ -36,7 +36,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public SwitchCaseRemovalTest(TestParameters parameters) {
@@ -59,7 +59,7 @@
})
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::verifyOutput)
.run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index 179333f..2cbef3e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -36,7 +36,6 @@
@Test
public void testCompanionAndRegularObjects() throws Exception {
- expectThrowsWithHorizontalClassMerging();
assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
final String mainClassName = "class_staticizer.MainKt";
@@ -47,7 +46,6 @@
false,
app -> {
CodeInspector inspector = new CodeInspector(app);
- assertThat(inspector.clazz("class_staticizer.Regular$Companion"), isPresent());
assertThat(inspector.clazz("class_staticizer.Derived$Companion"), isPresent());
// The Util class is there, but its instance methods have been inlined.
@@ -67,7 +65,6 @@
app -> {
CodeInspector inspector = new CodeInspector(app);
assertThat(inspector.clazz("class_staticizer.Regular$Companion"), not(isPresent()));
- assertThat(inspector.clazz("class_staticizer.Derived$Companion"), not(isPresent()));
ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
assertThat(utilClass, isPresent());
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index 1eca176..a440523 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils.codeinspector;
import static com.android.tools.r8.TestBase.toDexType;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -22,6 +23,13 @@
this.horizontallyMergedClasses = horizontallyMergedClasses;
}
+ public HorizontallyMergedClassesInspector assertMergedInto(Class<?> from, Class<?> target) {
+ assertEquals(
+ horizontallyMergedClasses.getMergeTargetOrDefault(toDexType(from, dexItemFactory)),
+ toDexType(target, dexItemFactory));
+ return this;
+ }
+
public HorizontallyMergedClassesInspector assertNoClassesMerged() {
assertTrue(horizontallyMergedClasses.getSources().isEmpty());
return this;