Initial experimental support for horizontal interface merging

Bug: 173990042
Change-Id: I6591e22b2f8ba9788b6e57187bcf6d74c51e46f5
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index 07410c0..684dd7d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -16,6 +16,7 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -66,6 +67,14 @@
     return this;
   }
 
+  public DexTypeList map(Function<DexType, DexType> fn) {
+    if (isEmpty()) {
+      return DexTypeList.empty();
+    }
+    DexType[] newTypes = ArrayUtils.map(values, fn, DexType.EMPTY_ARRAY);
+    return newTypes != values ? create(newTypes) : this;
+  }
+
   public DexTypeList removeIf(Predicate<DexType> predicate) {
     return keepIf(not(predicate));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 686d7d5..457ef38 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.shaking.GraphReporter;
@@ -461,9 +462,8 @@
               assert false;
               return;
             }
-            assert !instantiatedLambdas.containsKey(type);
             // TODO(b/150277553): Rewrite lambda descriptor.
-            instantiatedLambdas.put(type, lambdas);
+            instantiatedLambdas.computeIfAbsent(type, ignoreKey(ArrayList::new)).addAll(lambdas);
           });
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 880890d..23fc39a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.KeepClassInfo;
 import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -247,10 +248,25 @@
   private void mergeInterfaces() {
     DexTypeList previousInterfaces = group.getTarget().getInterfaces();
     Set<DexType> interfaces = Sets.newLinkedHashSet(previousInterfaces);
-    group.forEachSource(clazz -> Iterables.addAll(interfaces, clazz.getInterfaces()));
-    if (interfaces.size() > previousInterfaces.size()) {
-      group.getTarget().setInterfaces(new DexTypeList(interfaces));
+    if (group.isInterfaceGroup()) {
+      // Add all implemented interfaces from the merge group to the target class, ignoring
+      // implemented interfaces that are part of the merge group.
+      Set<DexType> groupTypes =
+          SetUtils.newImmutableSet(
+              builder -> group.forEach(clazz -> builder.accept(clazz.getType())));
+      group.forEachSource(
+          clazz -> {
+            for (DexType itf : clazz.getInterfaces()) {
+              if (!groupTypes.contains(itf)) {
+                interfaces.add(itf);
+              }
+            }
+          });
+    } else {
+      // Add all implemented interfaces from the merge group to the target class.
+      group.forEachSource(clazz -> Iterables.addAll(interfaces, clazz.getInterfaces()));
     }
+    group.getTarget().setInterfaces(DexTypeList.create(interfaces));
   }
 
   void mergeInstanceFields() {
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 eff9460..c94039b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -17,21 +17,23 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects;
 import com.android.tools.r8.horizontalclassmerging.policies.NoDeadEnumLiteMaps;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodCollisions;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodMerging;
 import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
 import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFieldAnnotations;
-import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
 import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
 import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
 import com.android.tools.r8.horizontalclassmerging.policies.NoServiceLoaders;
 import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
 import com.android.tools.r8.horizontalclassmerging.policies.NotVerticallyMergedIntoSubtype;
+import com.android.tools.r8.horizontalclassmerging.policies.OnlyDirectlyConnectedOrUnrelatedInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
+import com.android.tools.r8.horizontalclassmerging.policies.PreventClassMethodAndDefaultMethodCollisions;
 import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoDifferentMainDexGroups;
-import com.android.tools.r8.horizontalclassmerging.policies.PreventMethodImplementation;
 import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
 import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
 import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields;
@@ -45,7 +47,7 @@
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 
 public class HorizontalClassMerger {
@@ -68,12 +70,9 @@
   }
 
   public HorizontalClassMergerResult run(RuntimeTypeCheckInfo runtimeTypeCheckInfo, Timing timing) {
-    MergeGroup initialGroup = new MergeGroup(appView.appInfo().classesWithDeterministicOrder());
-
     // Run the policies on all program classes to produce a final grouping.
     List<Policy> policies = getPolicies(runtimeTypeCheckInfo);
-    Collection<MergeGroup> groups =
-        new PolicyExecutor().run(Collections.singletonList(initialGroup), policies, timing);
+    Collection<MergeGroup> groups = new PolicyExecutor().run(getInitialGroups(), policies, timing);
 
     // If there are no groups, then end horizontal class merging.
     if (groups.isEmpty()) {
@@ -126,6 +125,25 @@
     return builder.build();
   }
 
+  private List<MergeGroup> getInitialGroups() {
+    MergeGroup initialClassGroup = new MergeGroup();
+    MergeGroup initialInterfaceGroup = new MergeGroup();
+    for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
+      if (clazz.isInterface()) {
+        if (appView.options().horizontalClassMergerOptions().isInterfaceMergingEnabled()) {
+          initialInterfaceGroup.add(clazz);
+        }
+      } else {
+        initialClassGroup.add(clazz);
+      }
+    }
+    List<MergeGroup> initialGroups = new LinkedList<>();
+    initialGroups.add(initialClassGroup);
+    initialGroups.add(initialInterfaceGroup);
+    initialGroups.removeIf(MergeGroup::isTrivial);
+    return initialGroups;
+  }
+
   private List<Policy> getPolicies(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
     AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
     List<SingleClassPolicy> singleClassPolicies =
@@ -136,23 +154,22 @@
             new NoEnums(appView),
             new NoInnerClasses(),
             new NoInstanceFieldAnnotations(),
-            new NoInterfaces(),
             new NoClassInitializerWithObservableSideEffects(),
             new NoNativeMethods(),
             new NoKeepRules(appView),
             new NoKotlinMetadata(),
             new NoServiceLoaders(appView),
             new NotVerticallyMergedIntoSubtype(appView),
-            new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo),
+            new NoDirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
             new DontInlinePolicy(appViewWithLiveness));
-    List<MultiClassPolicy> multiClassPolicies =
+    List<Policy> multiClassPolicies =
         ImmutableList.of(
             new SameInstanceFields(appView),
             new NoClassAnnotationCollisions(),
             new CheckAbstractClasses(appView),
             new SyntheticItemsPolicy(appView),
             new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
-            new PreventMethodImplementation(appView),
+            new PreventClassMethodAndDefaultMethodCollisions(appView),
             new PreventMergeIntoDifferentMainDexGroups(appView),
             new AllInstantiatedOrUninstantiated(appViewWithLiveness),
             new SameParentClass(),
@@ -162,6 +179,9 @@
             new RespectPackageBoundaries(appView),
             new DontMergeSynchronizedClasses(appViewWithLiveness),
             new MinimizeFieldCasts(),
+            new OnlyDirectlyConnectedOrUnrelatedInterfaces(appView),
+            new NoDefaultInterfaceMethodMerging(appView),
+            new NoDefaultInterfaceMethodCollisions(appView),
             new LimitGroups(appView));
     return ImmutableList.<Policy>builder()
         .addAll(singleClassPolicies)
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index b99cb2f..5c7e60f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -125,6 +125,11 @@
     return size() < 2;
   }
 
+  public boolean isNonTrivial() {
+    return !isTrivial();
+  }
+
+  @Override
   public boolean isEmpty() {
     return classes.isEmpty();
   }
@@ -140,6 +145,7 @@
     return classes.iterator();
   }
 
+  @Override
   public int size() {
     return classes.size();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
index b21f233..63420fe 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
@@ -4,21 +4,11 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
-import java.util.ArrayList;
 import java.util.Collection;
 
 public abstract class MultiClassPolicy extends Policy {
 
   /**
-   * Remove all groups containing no or only a single class, as there is no point in merging these.
-   */
-  protected Collection<MergeGroup> removeTrivialGroups(Collection<MergeGroup> groups) {
-    assert !(groups instanceof ArrayList);
-    groups.removeIf(MergeGroup::isTrivial);
-    return groups;
-  }
-
-  /**
    * Apply the multi class policy to a group of program classes.
    *
    * @param group This is a group of program classes which can currently still be merged.
@@ -27,4 +17,14 @@
    *     cannot be merged with any other classes they are returned as singleton lists.
    */
   public abstract Collection<MergeGroup> apply(MergeGroup group);
+
+  @Override
+  public boolean isMultiClassPolicy() {
+    return true;
+  }
+
+  @Override
+  public MultiClassPolicy asMultiClassPolicy() {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
index e06a7ef..067fcd5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
@@ -20,4 +20,14 @@
   public abstract Collection<MergeGroup> apply(MergeGroup group, T data);
 
   public abstract T preprocess(Collection<MergeGroup> groups);
+
+  @Override
+  public boolean isMultiClassPolicyWithPreprocessing() {
+    return true;
+  }
+
+  @Override
+  public MultiClassPolicyWithPreprocessing<?> asMultiClassPolicyWithPreprocessing() {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
index b984ed5..e5854fe 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
@@ -4,19 +4,78 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import java.util.ArrayList;
+import java.util.Collection;
+
 /**
  * The super class of all horizontal class merging policies. Most classes will either implement
  * {@link SingleClassPolicy} or {@link MultiClassPolicy}.
  */
 public abstract class Policy {
+
   /** Counter keeping track of how many classes this policy has removed. For debugging only. */
   public int numberOfRemovedClasses;
 
+  public int numberOfRemovedInterfaces;
+
   public void clear() {}
 
   public abstract String getName();
 
+  public boolean isSingleClassPolicy() {
+    return false;
+  }
+
+  public SingleClassPolicy asSingleClassPolicy() {
+    return null;
+  }
+
+  public boolean isMultiClassPolicy() {
+    return false;
+  }
+
+  public MultiClassPolicy asMultiClassPolicy() {
+    return null;
+  }
+
+  public boolean isMultiClassPolicyWithPreprocessing() {
+    return false;
+  }
+
+  public MultiClassPolicyWithPreprocessing<?> asMultiClassPolicyWithPreprocessing() {
+    return null;
+  }
+
   public boolean shouldSkipPolicy() {
     return false;
   }
+
+  /**
+   * Remove all groups containing no or only a single class, as there is no point in merging these.
+   */
+  protected Collection<MergeGroup> removeTrivialGroups(Collection<MergeGroup> groups) {
+    assert !(groups instanceof ArrayList);
+    groups.removeIf(MergeGroup::isTrivial);
+    return groups;
+  }
+
+  boolean recordRemovedClassesForDebugging(
+      boolean isInterfaceGroup, int previousGroupSize, Collection<MergeGroup> newGroups) {
+    assert previousGroupSize >= 2;
+    int previousNumberOfRemovedClasses = previousGroupSize - 1;
+    int newNumberOfRemovedClasses = 0;
+    for (MergeGroup newGroup : newGroups) {
+      if (newGroup.isNonTrivial()) {
+        newNumberOfRemovedClasses += newGroup.size() - 1;
+      }
+    }
+    assert previousNumberOfRemovedClasses >= newNumberOfRemovedClasses;
+    int change = previousNumberOfRemovedClasses - newNumberOfRemovedClasses;
+    if (isInterfaceGroup) {
+      numberOfRemovedInterfaces += change;
+    } else {
+      numberOfRemovedClasses += change;
+    }
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
index 33b6f7f..c1c4f64 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
-import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -17,15 +17,16 @@
  */
 public class PolicyExecutor {
 
-  // TODO(b/165506334): if performing mutable operation ensure that linked lists are used
   private void applySingleClassPolicy(SingleClassPolicy policy, LinkedList<MergeGroup> groups) {
     Iterator<MergeGroup> i = groups.iterator();
     while (i.hasNext()) {
       MergeGroup group = i.next();
-      int previousNumberOfClasses = group.size();
+      boolean isInterfaceGroup = group.isInterfaceGroup();
+      int previousGroupSize = group.size();
       group.removeIf(clazz -> !policy.canMerge(clazz));
-      policy.numberOfRemovedClasses += previousNumberOfClasses - group.size();
-      if (group.size() < 2) {
+      assert policy.recordRemovedClassesForDebugging(
+          isInterfaceGroup, previousGroupSize, ImmutableList.of(group));
+      if (group.isTrivial()) {
         i.remove();
       }
     }
@@ -37,11 +38,30 @@
     LinkedList<MergeGroup> newGroups = new LinkedList<>();
     groups.forEach(
         group -> {
-          int previousNumberOfClasses = group.size();
+          boolean isInterfaceGroup = group.isInterfaceGroup();
+          int previousGroupSize = group.size();
           Collection<MergeGroup> policyGroups = policy.apply(group);
           policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
-          policy.numberOfRemovedClasses +=
-              previousNumberOfClasses - IterableUtils.sumInt(policyGroups, MergeGroup::size);
+          assert policy.recordRemovedClassesForDebugging(
+              isInterfaceGroup, previousGroupSize, policyGroups);
+          newGroups.addAll(policyGroups);
+        });
+    return newGroups;
+  }
+
+  private <T> LinkedList<MergeGroup> applyMultiClassPolicyWithPreprocessing(
+      MultiClassPolicyWithPreprocessing<T> policy, LinkedList<MergeGroup> groups) {
+    // For each group apply the multi class policy and add all the new groups together.
+    T data = policy.preprocess(groups);
+    LinkedList<MergeGroup> newGroups = new LinkedList<>();
+    groups.forEach(
+        group -> {
+          boolean isInterfaceGroup = group.isInterfaceGroup();
+          int previousGroupSize = group.size();
+          Collection<MergeGroup> policyGroups = policy.apply(group, data);
+          policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
+          assert policy.recordRemovedClassesForDebugging(
+              isInterfaceGroup, previousGroupSize, policyGroups);
           newGroups.addAll(policyGroups);
         });
     return newGroups;
@@ -68,11 +88,15 @@
       }
 
       timing.begin(policy.getName());
-      if (policy instanceof SingleClassPolicy) {
-        applySingleClassPolicy((SingleClassPolicy) policy, linkedGroups);
+      if (policy.isSingleClassPolicy()) {
+        applySingleClassPolicy(policy.asSingleClassPolicy(), linkedGroups);
+      } else if (policy.isMultiClassPolicy()) {
+        linkedGroups = applyMultiClassPolicy(policy.asMultiClassPolicy(), linkedGroups);
       } else {
-        assert policy instanceof MultiClassPolicy;
-        linkedGroups = applyMultiClassPolicy((MultiClassPolicy) policy, linkedGroups);
+        assert policy.isMultiClassPolicyWithPreprocessing();
+        linkedGroups =
+            applyMultiClassPolicyWithPreprocessing(
+                policy.asMultiClassPolicyWithPreprocessing(), linkedGroups);
       }
       timing.end();
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
index b0757c5..db2d8eb 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
@@ -13,4 +13,14 @@
    * @return {@code false} if the class should not be merged, otherwise {@code true}.
    */
   public abstract boolean canMerge(DexProgramClass program);
+
+  @Override
+  public boolean isSingleClassPolicy() {
+    return true;
+  }
+
+  @Override
+  public SingleClassPolicy asSingleClassPolicy() {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 53ca8c8..34c7362 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.shaking.AnnotationFixer;
@@ -115,8 +116,7 @@
   public HorizontalClassMergerGraphLens fixupTypeReferences() {
     List<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
     Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
-
-    classes.forEach(this::fixupProgramClassSuperType);
+    classes.forEach(this::fixupProgramClassSuperTypes);
     SubtypingForrestForClasses subtypingForrest = new SubtypingForrestForClasses(appView);
     // TODO(b/170078037): parallelize this code segment.
     for (DexProgramClass root : subtypingForrest.getProgramRoots()) {
@@ -127,8 +127,9 @@
     return lens;
   }
 
-  private void fixupProgramClassSuperType(DexProgramClass clazz) {
+  private void fixupProgramClassSuperTypes(DexProgramClass clazz) {
     clazz.superType = fixupType(clazz.superType);
+    clazz.setInterfaces(fixupInterfaces(clazz, clazz.getInterfaces()));
   }
 
   private BiMap<DexMethodSignature, DexMethodSignature> fixupProgramClass(
@@ -197,10 +198,6 @@
 
   private void fixupInterfaceClass(DexProgramClass iface) {
     Set<DexMethodSignature> newDirectMethods = new LinkedHashSet<>();
-
-    assert iface.superType == dexItemFactory.objectType;
-    iface.superType = mergedClasses.getMergeTargetOrDefault(iface.superType);
-
     iface
         .getMethodCollection()
         .replaceDirectMethods(method -> fixupDirectMethod(newDirectMethods, method));
@@ -210,6 +207,16 @@
     lensBuilder.commitPendingUpdates();
   }
 
+  private DexTypeList fixupInterfaces(DexProgramClass clazz, DexTypeList interfaceTypes) {
+    Set<DexType> seen = Sets.newIdentityHashSet();
+    return interfaceTypes.map(
+        interfaceType -> {
+          DexType rewrittenInterfaceType = mapClassType(interfaceType);
+          assert rewrittenInterfaceType != clazz.getType();
+          return seen.add(rewrittenInterfaceType) ? rewrittenInterfaceType : null;
+        });
+  }
+
   private DexEncodedMethod fixupProgramMethod(
       DexMethod newMethodReference, DexEncodedMethod method) {
     DexMethod originalMethodReference = method.getReference();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
index 0851469..11f8867 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static java.util.Collections.emptyMap;
 import static java.util.Collections.emptySet;
 
@@ -197,7 +198,7 @@
             });
     inheritedClassMethodsPerClass
         .keySet()
-        .removeIf(type -> !appView.definitionFor(type).isProgramClass());
+        .removeIf(type -> asProgramClassOrNull(appView.definitionFor(type)) == null);
     return inheritedClassMethodsPerClass;
   }
 
@@ -252,7 +253,7 @@
             });
     inheritedDefaultMethodsPerType
         .keySet()
-        .removeIf(type -> !appView.definitionFor(type).isProgramClass());
+        .removeIf(type -> asProgramClassOrNull(appView.definitionFor(type)) == null);
     return inheritedDefaultMethodsPerType;
   }
 
@@ -296,7 +297,7 @@
             });
     defaultMethodsInheritedBySubclassesPerClass
         .keySet()
-        .removeIf(type -> !appView.definitionFor(type).isProgramClass());
+        .removeIf(type -> asProgramClassOrNull(appView.definitionFor(type)) == null);
     return defaultMethodsInheritedBySubclassesPerClass;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
index 17659f8..5968d79 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
@@ -4,14 +4,19 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
+import com.android.tools.r8.utils.InternalOptions;
 
 public class NoDirectRuntimeTypeChecks extends SingleClassPolicy {
+
+  private final InternalOptions options;
   private final RuntimeTypeCheckInfo runtimeTypeCheckInfo;
 
-  public NoDirectRuntimeTypeChecks(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+  public NoDirectRuntimeTypeChecks(AppView<?> appView, RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+    this.options = appView.options();
     this.runtimeTypeCheckInfo = runtimeTypeCheckInfo;
   }
 
@@ -24,4 +29,9 @@
   public String getName() {
     return "NoDirectRuntimeTypeChecks";
   }
+
+  @Override
+  public boolean shouldSkipPolicy() {
+    return options.horizontalClassMergerOptions().isIgnoreRuntimeTypeChecksForTestingEnabled();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
deleted file mode 100644
index d6032e7..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// 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.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-
-public class NoInterfaces extends SingleClassPolicy {
-
-  @Override
-  public boolean canMerge(DexProgramClass program) {
-    return !program.isInterface();
-  }
-
-  @Override
-  public String getName() {
-    return "NoInterfaces";
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
similarity index 92%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
index 5bffb33..9ec219c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
 import com.android.tools.r8.horizontalclassmerging.SubtypingForrestForClasses;
 import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
@@ -48,7 +49,7 @@
  *
  * <p>See: https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-5.html#jvms-5.4.3.3)
  */
-public class PreventMethodImplementation extends MultiClassPolicy {
+public class PreventClassMethodAndDefaultMethodCollisions extends MultiClassPolicy {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final SubtypingForrestForClasses subtypingForrestForClasses;
@@ -62,7 +63,7 @@
 
   @Override
   public String getName() {
-    return "PreventMethodImplementation";
+    return "PreventClassMethodAndDefaultMethodCollisions";
   }
 
   private abstract static class SignaturesCache<C extends DexClass> {
@@ -124,7 +125,8 @@
     }
   }
 
-  public PreventMethodImplementation(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public PreventClassMethodAndDefaultMethodCollisions(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
     this.subtypingForrestForClasses = new SubtypingForrestForClasses(appView);
   }
@@ -150,6 +152,11 @@
 
   @Override
   public Collection<MergeGroup> apply(MergeGroup group) {
+    // This policy is specific to issues that may arise from merging (non-interface) classes.
+    if (group.isInterfaceGroup()) {
+      return ImmutableList.of(group);
+    }
+
     DexMethodSignatureSet signatures = DexMethodSignatureSet.createLinked();
     for (DexProgramClass clazz : group) {
       signatures.addAllMethods(clazz.methods());
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 5a4ee11..aae7abe 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -93,10 +93,21 @@
    * @param clazz target type's Class to cast
    * @param original an array of original elements
    * @param mapper a mapper that rewrites an original element to a new one, maybe `null`
-   * @param <T> target type
    * @return an array with written elements
    */
   public static <T> T[] map(Class<T[]> clazz, T[] original, Function<T, T> mapper) {
+    return map(original, mapper, clazz.cast(Array.newInstance(clazz.getComponentType(), 0)));
+  }
+
+  /**
+   * Rewrites the input array based on the given function.
+   *
+   * @param original an array of original elements
+   * @param mapper a mapper that rewrites an original element to a new one, maybe `null`
+   * @param emptyArray an empty array
+   * @return an array with written elements
+   */
+  public static <T> T[] map(T[] original, Function<T, T> mapper, T[] emptyArray) {
     ArrayList<T> results = null;
     for (int i = 0; i < original.length; i++) {
       T oldOne = original[i];
@@ -117,11 +128,7 @@
         }
       }
     }
-    if (results == null) {
-      return original;
-    }
-    return results.toArray(
-        clazz.cast(Array.newInstance(clazz.getComponentType(), results.size())));
+    return results != null ? results.toArray(emptyArray) : original;
   }
 
   public static int[] createIdentityArray(int size) {
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 40ce5b8..17aaf3e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1200,6 +1200,7 @@
             || System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null;
     private boolean enableConstructorMerging = true;
     private boolean enableInterfaceMerging = false;
+    private boolean ignoreRuntimeTypeChecksForTesting = false;
 
     public int maxGroupSize = 30;
 
@@ -1215,6 +1216,10 @@
       this.enable = enable;
     }
 
+    public void enableInterfaceMerging() {
+      enableInterfaceMerging = true;
+    }
+
     public int getMaxGroupSize() {
       return maxGroupSize;
     }
@@ -1231,9 +1236,17 @@
       return enable;
     }
 
+    public boolean isIgnoreRuntimeTypeChecksForTestingEnabled() {
+      return ignoreRuntimeTypeChecksForTesting;
+    }
+
     public boolean isInterfaceMergingEnabled() {
       return enableInterfaceMerging;
     }
+
+    public void setIgnoreRuntimeTypeChecksForTesting() {
+      ignoreRuntimeTypeChecksForTesting = true;
+    }
   }
 
   public static class ProtoShrinkingOptions {
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index c2cf607..63e881e 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -9,6 +9,7 @@
 import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.IntFunction;
+import java.util.function.Supplier;
 
 public class MapUtils {
 
@@ -26,6 +27,10 @@
     return map.values().iterator().next();
   }
 
+  public static <T, R> Function<T, R> ignoreKey(Supplier<R> supplier) {
+    return ignore -> supplier.get();
+  }
+
   public static <K, V> Map<K, V> map(
       Map<K, V> map,
       IntFunction<Map<K, V>> factory,
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
index a71c116..8ef5506 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
@@ -44,6 +44,7 @@
     return EMPTY;
   }
 
+  @Override
   public boolean add(DexMethodSignature signature) {
     return backing.add(signature);
   }
@@ -135,10 +136,6 @@
     return backing.removeAll(collection);
   }
 
-  public void removeAll(Iterable<DexMethodSignature> signatures) {
-    signatures.forEach(this::remove);
-  }
-
   public void removeAllMethods(Iterable<DexEncodedMethod> methods) {
     methods.forEach(this::remove);
   }