Add feature split policy and test
Bug: 165727167
Bug: 165000217
Change-Id: I0ced79922dad3dac8979268f1e24703431d34b2d
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index c993870..4694c9c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDex;
import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
+import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ClassMergingEnqueuerExtension;
@@ -56,6 +57,7 @@
new NotEntryPoint(appView.dexItemFactory()),
new PreventMergeIntoMainDex(appView, mainDexTracingResult),
new SameParentClass(),
+ new SameFeatureSplit(appView),
new RespectPackageBoundaries(appView),
new DontMergeSynchronizedClasses(appView)
// TODO: add policies
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
new file mode 100644
index 0000000..3407306
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
@@ -0,0 +1,26 @@
+// 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.horizontalclassmerging;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+public abstract class MultiClassSameReferencePolicy<T> extends MultiClassPolicy {
+
+ @Override
+ public final Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) {
+ Map<T, Collection<DexProgramClass>> groups = new IdentityHashMap<>();
+ for (DexProgramClass clazz : group) {
+ groups.computeIfAbsent(getMergeKey(clazz), ignore -> new LinkedList<>()).add(clazz);
+ }
+ removeTrivialGroups(groups.values());
+ return groups.values();
+ }
+
+ public abstract T getMergeKey(DexProgramClass clazz);
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
new file mode 100644
index 0000000..9e4dd2b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
@@ -0,0 +1,24 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class SameFeatureSplit extends MultiClassSameReferencePolicy<FeatureSplit> {
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public SameFeatureSplit(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public FeatureSplit getMergeKey(DexProgramClass clazz) {
+ return appView.appInfo().getClassToFeatureSplitMap().getFeatureSplit(clazz);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java
new file mode 100644
index 0000000..5907c150
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java
@@ -0,0 +1,130 @@
+// 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.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runners.Parameterized;
+
+public class ClassesWithFeatureSplitTest extends HorizontalClassMergingTestBase {
+ public ClassesWithFeatureSplitTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Base.class)
+ .addFeatureSplitRuntime()
+ .addFeatureSplit(Feature1Class1.class, Feature1Class2.class, Feature1Main.class)
+ .addFeatureSplit(Feature2Class.class, Feature2Main.class)
+ .addKeepFeatureMainRule(Feature1Main.class)
+ .addKeepFeatureMainRule(Feature2Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspectBase, this::inspectFeature1, this::inspectFeature2);
+
+ compileResult
+ .runFeature(parameters.getRuntime(), Feature1Main.class, compileResult.getFeature(0))
+ .assertSuccessWithOutputLines("base", "feature 1 class 1", "feature 1 class 2");
+
+ compileResult
+ .runFeature(parameters.getRuntime(), Feature2Main.class, compileResult.getFeature(1))
+ .assertSuccessWithOutputLines("base", "feature 2");
+ }
+
+ private void inspectBase(CodeInspector inspector) {
+ assertThat(inspector.clazz(Base.class), isPresent());
+ assertThat(inspector.clazz(Feature1Class1.class), not(isPresent()));
+ assertThat(inspector.clazz(Feature1Class2.class), not(isPresent()));
+ assertThat(inspector.clazz(Feature2Class.class), not(isPresent()));
+ }
+
+ private void inspectFeature1(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature1Main.class), isPresent());
+ assertThat(inspector.clazz(Feature1Class1.class), isPresent());
+ assertThat(
+ inspector.clazz(Feature1Class2.class), notIf(isPresent(), enableHorizontalClassMerging));
+ assertThat(inspector.clazz(Feature2Main.class), not(isPresent()));
+ assertThat(inspector.clazz(Feature2Class.class), not(isPresent()));
+ }
+
+ private void inspectFeature2(CodeInspector inspector) {
+ assertThat(inspector.clazz(Feature1Main.class), not(isPresent()));
+ assertThat(inspector.clazz(Feature1Class1.class), not(isPresent()));
+ assertThat(inspector.clazz(Feature1Class2.class), not(isPresent()));
+ assertThat(inspector.clazz(Feature2Main.class), isPresent());
+ assertThat(inspector.clazz(Feature2Class.class), isPresent());
+ }
+
+ @NeverClassInline
+ public static class Base {
+ public Base() {
+ System.out.println("base");
+ }
+ }
+
+ @NeverClassInline
+ public static class Feature1Class1 {
+ public Feature1Class1() {
+ System.out.println("feature 1 class 1");
+ }
+ }
+
+ @NeverClassInline
+ public static class Feature1Class2 {
+ public Feature1Class2() {
+ System.out.println("feature 1 class 2");
+ }
+ }
+
+ @NeverClassInline
+ public static class Feature2Class {
+ public Feature2Class() {
+ System.out.println("feature 2");
+ }
+ }
+
+ public static class Feature1Main implements RunInterface {
+
+ @Override
+ public void run() {
+ new Base();
+ new Feature1Class1();
+ new Feature1Class2();
+ }
+ }
+
+ public static class Feature2Main implements RunInterface {
+
+ @Override
+ public void run() {
+ new Base();
+ new Feature2Class();
+ }
+ }
+}