Bound number of interface methods after interface merging

Bug: 205166439
Change-Id: Ia22209b4ec08ef26af9e3401d0edbce842f59d06
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index fbbe362..5e76e2d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.CheckSyntheticClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.FinalizeMergeGroup;
 import com.android.tools.r8.horizontalclassmerging.policies.LimitClassGroups;
+import com.android.tools.r8.horizontalclassmerging.policies.LimitInterfaceGroups;
 import com.android.tools.r8.horizontalclassmerging.policies.MinimizeInstanceFieldCasts;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoCheckDiscard;
@@ -229,6 +230,7 @@
     builder.add(
         new NoDefaultInterfaceMethodMerging(appView, mode),
         new NoDefaultInterfaceMethodCollisions(appView, mode),
+        new LimitInterfaceGroups(appView),
         new OnlyDirectlyConnectedOrUnrelatedInterfaces(appView, mode));
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
index 210f21d..2752544 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
@@ -18,7 +18,7 @@
   private final int maxGroupSize;
 
   public LimitClassGroups(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    maxGroupSize = appView.options().horizontalClassMergerOptions().getMaxGroupSize();
+    maxGroupSize = appView.options().horizontalClassMergerOptions().getMaxClassGroupSize();
     assert maxGroupSize >= 2;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitInterfaceGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitInterfaceGroups.java
new file mode 100644
index 0000000..adee1f4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitInterfaceGroups.java
@@ -0,0 +1,63 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class LimitInterfaceGroups extends MultiClassPolicy {
+
+  private final int maxGroupSize;
+
+  public LimitInterfaceGroups(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    maxGroupSize = appView.options().horizontalClassMergerOptions().getMaxInterfaceGroupSize();
+    assert maxGroupSize >= 0;
+  }
+
+  @Override
+  public Collection<MergeGroup> apply(MergeGroup group) {
+    if (group.isClassGroup()) {
+      return Collections.singletonList(group);
+    }
+    // Mapping from new merge groups to their size.
+    Map<MergeGroup, Integer> newGroups = new LinkedHashMap<>();
+    for (DexProgramClass clazz : group) {
+      processClass(clazz, newGroups);
+    }
+    return removeTrivialGroups(newGroups.keySet());
+  }
+
+  private void processClass(DexProgramClass clazz, Map<MergeGroup, Integer> newGroups) {
+    int increment = clazz.getMethodCollection().size();
+
+    // Find an existing group.
+    for (Entry<MergeGroup, Integer> entry : newGroups.entrySet()) {
+      MergeGroup candidateGroup = entry.getKey();
+      int candidateGroupSize = entry.getValue();
+      int newCandidateGroupSize = candidateGroupSize + increment;
+      if (newCandidateGroupSize <= maxGroupSize) {
+        candidateGroup.add(clazz);
+        entry.setValue(newCandidateGroupSize);
+        return;
+      }
+    }
+
+    // Failed to find an existing group.
+    newGroups.put(new MergeGroup(clazz), increment);
+  }
+
+  @Override
+  public String getName() {
+    return "LimitInterfaceGroups";
+  }
+}
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 727f9c9..ea7c922 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1381,8 +1381,6 @@
     private boolean ignoreRuntimeTypeChecksForTesting = false;
     private boolean restrictToSynthetics = false;
 
-    public int maxGroupSize = 30;
-
     public void disable() {
       enable = false;
     }
@@ -1399,8 +1397,12 @@
       this.enable = enable;
     }
 
-    public int getMaxGroupSize() {
-      return maxGroupSize;
+    public int getMaxClassGroupSize() {
+      return 30;
+    }
+
+    public int getMaxInterfaceGroupSize() {
+      return 100;
     }
 
     public boolean isConstructorMergingEnabled() {
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
index c1da822..89f57c8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
@@ -73,7 +73,8 @@
                           .allMatch(
                               mergeGroup ->
                                   mergeGroup.size()
-                                      <= defaultHorizontalClassMergerOptions.getMaxGroupSize()));
+                                      <= defaultHorizontalClassMergerOptions
+                                          .getMaxClassGroupSize()));
                 })
             .allowDiagnosticWarningMessages()
             .compile()