Enable extra round of class merging

This also:

* enables class merging for all synthetics in the final round of class merging (cf. SyntheticItems.isEligibleForClassMerging()),

* moves the merging of interfaces to the final round of class merging (cf. InternalOptions.isInterfaceMergingEnabled()).

Bug: 181846319
Change-Id: I98c88ad4739192788d68627eba1ed5b555951919
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index d9df6e8..2067ad6 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -92,7 +92,7 @@
   private boolean allCodeProcessed = false;
   private Predicate<DexType> classesEscapingIntoLibrary = Predicates.alwaysTrue();
   private InitializedClassesInInstanceMethods initializedClassesInInstanceMethods;
-  private HorizontallyMergedClasses horizontallyMergedClasses;
+  private HorizontallyMergedClasses horizontallyMergedClasses = HorizontallyMergedClasses.empty();
   private VerticallyMergedClasses verticallyMergedClasses;
   private EnumDataMap unboxedEnums = EnumDataMap.empty();
   // TODO(b/169115389): Remove
@@ -497,7 +497,7 @@
 
   public MergedClassesCollection allMergedClasses() {
     MergedClassesCollection collection = new MergedClassesCollection();
-    if (horizontallyMergedClasses != null) {
+    if (hasHorizontallyMergedClasses()) {
       collection.add(horizontallyMergedClasses);
     }
     if (verticallyMergedClasses != null) {
@@ -507,7 +507,7 @@
   }
 
   public boolean hasHorizontallyMergedClasses() {
-    return horizontallyMergedClasses != null;
+    return !horizontallyMergedClasses.isEmpty();
   }
 
   /**
@@ -520,15 +520,12 @@
 
   public void setHorizontallyMergedClasses(
       HorizontallyMergedClasses horizontallyMergedClasses, HorizontalClassMerger.Mode mode) {
-    if (mode.isInitial()) {
-      assert !hasHorizontallyMergedClasses();
-      this.horizontallyMergedClasses = horizontallyMergedClasses;
+    assert !hasHorizontallyMergedClasses() || mode.isFinal();
+    this.horizontallyMergedClasses = horizontallyMergedClasses().extend(horizontallyMergedClasses);
+    if (mode.isFinal()) {
       testing()
           .horizontallyMergedClassesConsumer
-          .accept(dexItemFactory(), horizontallyMergedClasses);
-    } else {
-      // TODO(b/187496738): Enable final class merging.
-      assert horizontallyMergedClasses.isEmpty();
+          .accept(dexItemFactory(), horizontallyMergedClasses());
     }
   }
 
@@ -650,7 +647,7 @@
 
   public void rewriteWithLens(NonIdentityGraphLens lens) {
     if (lens != null) {
-      rewriteWithLens(lens, appInfo().app().asDirect(), withLiveness(), lens.getPrevious());
+      rewriteWithLens(lens, appInfo().app().asDirect(), withClassHierarchy(), lens.getPrevious());
     }
   }
 
@@ -663,13 +660,13 @@
       NonIdentityGraphLens lens, DirectMappedDexApplication application, GraphLens appliedLens) {
     assert lens != null;
     assert application != null;
-    rewriteWithLens(lens, application, withLiveness(), appliedLens);
+    rewriteWithLens(lens, application, withClassHierarchy(), appliedLens);
   }
 
   private static void rewriteWithLens(
       NonIdentityGraphLens lens,
       DirectMappedDexApplication application,
-      AppView<AppInfoWithLiveness> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       GraphLens appliedLens) {
     if (lens == null) {
       return;
@@ -701,7 +698,11 @@
     firstUnappliedLens.withAlternativeParentLens(
         newMemberRebindingLens,
         () -> {
-          appView.setAppInfo(appView.appInfo().rewrittenWithLens(application, lens));
+          if (appView.hasLiveness()) {
+            appView
+                .withLiveness()
+                .setAppInfo(appView.appInfoWithLiveness().rewrittenWithLens(application, lens));
+          }
           appView.setAppServices(appView.appServices().rewrittenWithLens(lens));
           if (appView.hasInitClassLens()) {
             appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens));
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 88d0852..f87f10d 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -23,11 +23,13 @@
 import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -579,10 +581,16 @@
     return builder.build();
   }
 
-  public <T> ImmutableMap<DexType, T> rewriteTypeKeys(Map<DexType, T> map) {
-    ImmutableMap.Builder<DexType, T> builder = ImmutableMap.builder();
-    map.forEach((type, value) -> builder.put(lookupType(type), value));
-    return builder.build();
+  public <T> Map<DexType, T> rewriteTypeKeys(Map<DexType, T> map, BiFunction<T, T, T> merge) {
+    Map<DexType, T> newMap = new IdentityHashMap<>();
+    map.forEach(
+        (type, value) -> {
+          DexType rewrittenType = lookupType(type);
+          T previousValue = newMap.get(rewrittenType);
+          newMap.put(
+              rewrittenType, previousValue != null ? merge.apply(value, previousValue) : value);
+        });
+    return Collections.unmodifiableMap(newMap);
   }
 
   public boolean verifyMappingToOriginalProgram(
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 5eea854..2b65950 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
@@ -117,8 +117,7 @@
     }
 
     DexMethod newClinit = dexItemFactory.createClassInitializer(group.getTarget().getType());
-
-    CfCode code = classInitializerSynthesizedCode.synthesizeCode(group.getTarget().getType());
+    Code code = classInitializerSynthesizedCode.getOrCreateCode(group.getTarget().getType());
     if (!group.getTarget().hasClassInitializer()) {
       classMethodsBuilder.addDirectMethod(
           new DexEncodedMethod(
@@ -134,9 +133,11 @@
     } else {
       DexEncodedMethod clinit = group.getTarget().getClassInitializer();
       clinit.setCode(code, appView);
-      CfVersion cfVersion = classInitializerSynthesizedCode.getCfVersion();
-      if (cfVersion != null) {
-        clinit.upgradeClassFileVersion(cfVersion);
+      if (code.isCfCode()) {
+        CfVersion cfVersion = classInitializerSynthesizedCode.getCfVersion();
+        if (cfVersion != null) {
+          clinit.upgradeClassFileVersion(cfVersion);
+        }
       }
       classMethodsBuilder.addDirectMethod(clinit);
     }
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 df5f1a6..1a88983 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
-import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
 import com.android.tools.r8.utils.Timing;
 import java.util.ArrayList;
@@ -156,12 +155,7 @@
   private List<MergeGroup> getInitialGroups() {
     MergeGroup initialClassGroup = new MergeGroup();
     MergeGroup initialInterfaceGroup = new MergeGroup();
-    HorizontalClassMergerOptions options = appView.options().horizontalClassMergerOptions();
-    SyntheticItems syntheticItems = appView.getSyntheticItems();
     for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
-      if (options.isRestrictedToSynthetics() && !syntheticItems.isSyntheticClass(clazz)) {
-        continue;
-      }
       if (clazz.isInterface()) {
         initialInterfaceGroup.add(clazz);
       } else {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
index 67d390b..9caf1a9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -32,6 +32,24 @@
     return new HorizontallyMergedClasses(new EmptyBidirectionalOneToOneMap<>());
   }
 
+  public HorizontallyMergedClasses extend(HorizontallyMergedClasses newHorizontallyMergedClasses) {
+    if (isEmpty()) {
+      return newHorizontallyMergedClasses;
+    }
+    if (newHorizontallyMergedClasses.isEmpty()) {
+      return this;
+    }
+    Builder builder = builder();
+    forEachMergeGroup(
+        (sources, target) -> {
+          DexType rewrittenTarget = newHorizontallyMergedClasses.getMergeTargetOrDefault(target);
+          sources.forEach(source -> builder.add(source, rewrittenTarget));
+        });
+    newHorizontallyMergedClasses.forEachMergeGroup(
+        (sources, target) -> sources.forEach(source -> builder.add(source, target)));
+    return builder.build();
+  }
+
   @Override
   public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) {
     mergedClasses.forEachManyToOneMapping(consumer);
@@ -91,8 +109,13 @@
     private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedClasses =
         BidirectionalManyToOneHashMap.newIdentityHashMap();
 
+    void add(DexType source, DexType target) {
+      assert !mergedClasses.containsKey(source);
+      mergedClasses.put(source, target);
+    }
+
     void addMergeGroup(MergeGroup group) {
-      group.forEachSource(clazz -> mergedClasses.put(clazz.getType(), group.getTarget().getType()));
+      group.forEachSource(clazz -> add(clazz.getType(), group.getTarget().getType()));
     }
 
     Builder addMergeGroups(Iterable<MergeGroup> groups) {
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 57302300..1a5e5a7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
 import com.android.tools.r8.horizontalclassmerging.policies.AtMostOneClassInitializer;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.CheckSyntheticClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.LimitGroups;
 import com.android.tools.r8.horizontalclassmerging.policies.MinimizeInstanceFieldCasts;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
@@ -99,7 +100,7 @@
   private static void addRequiredSingleClassPolicies(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ImmutableList.Builder<SingleClassPolicy> builder) {
-    builder.add(new NoKeepRules(appView));
+    builder.add(new CheckSyntheticClasses(appView), new NoKeepRules(appView));
   }
 
   private static void addSingleClassPoliciesForMergingNonSyntheticClasses(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerSynthesizedCode.java
index 99d8f48..1b8a318 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerSynthesizedCode.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.CfVersionUtils;
@@ -58,7 +59,13 @@
     }
   }
 
-  public CfCode synthesizeCode(DexType originalHolder) {
+  public Code getOrCreateCode(DexType originalHolder) {
+    assert !staticClassInitializers.isEmpty();
+
+    if (staticClassInitializers.size() == 1) {
+      return staticClassInitializers.get(0).getCode();
+    }
+
     // Building the instructions will adjust maxStack and maxLocals. Build it here before invoking
     // the CfCode constructor to ensure that the value passed in is the updated values.
     List<CfInstruction> instructions = buildInstructions();
@@ -83,6 +90,11 @@
   }
 
   public CfVersion getCfVersion() {
+    if (staticClassInitializers.size() == 1) {
+      DexEncodedMethod method = staticClassInitializers.get(0);
+      return method.hasClassFileVersion() ? method.getClassFileVersion() : null;
+    }
+    assert staticClassInitializers.stream().allMatch(method -> method.getCode().isCfCode());
     return CfVersionUtils.max(staticClassInitializers);
   }
 
@@ -92,7 +104,6 @@
     public void add(DexEncodedMethod method) {
       assert method.isClassInitializer();
       assert method.hasCode();
-      assert method.getCode().isCfCode();
       staticClassInitializers.add(method);
     }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
new file mode 100644
index 0000000..8c08712
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
@@ -0,0 +1,39 @@
+// 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.AppInfoWithClassHierarchy;
+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.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
+
+public class CheckSyntheticClasses extends SingleClassPolicy {
+
+  private final HorizontalClassMergerOptions options;
+  private final SyntheticItems syntheticItems;
+
+  public CheckSyntheticClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.options = appView.options().horizontalClassMergerOptions();
+    this.syntheticItems = appView.getSyntheticItems();
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass clazz) {
+    if (!options.isSyntheticMergingEnabled() && syntheticItems.isSyntheticClass(clazz)) {
+      return false;
+    }
+    if (options.isRestrictedToSynthetics() && !syntheticItems.isSyntheticClass(clazz)) {
+      return false;
+    }
+    return true;
+  }
+
+  @Override
+  public String getName() {
+    return "CheckSyntheticClasses";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlySyntheticClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlySyntheticClasses.java
deleted file mode 100644
index fd1b2ab..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlySyntheticClasses.java
+++ /dev/null
@@ -1,30 +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.AppInfoWithClassHierarchy;
-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.synthesis.SyntheticItems;
-
-public class OnlySyntheticClasses extends SingleClassPolicy {
-
-  private final SyntheticItems syntheticItems;
-
-  public OnlySyntheticClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    this.syntheticItems = appView.getSyntheticItems();
-  }
-
-  @Override
-  public boolean canMerge(DexProgramClass clazz) {
-    return syntheticItems.isSyntheticClass(clazz);
-  }
-
-  @Override
-  public String getName() {
-    return "OnlySyntheticClasses";
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 41a298a..a6f246f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -141,6 +141,11 @@
       return !singleTarget.getDefinition().getOptimizationInfo().forceInline();
     }
 
+    if (!appView.testing().allowInliningOfSynthetics
+        && appView.getSyntheticItems().isSyntheticClass(singleTarget.getHolder())) {
+      return true;
+    }
+
     return false;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index 014337c..989c188 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
-import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
@@ -328,11 +327,9 @@
 
   private boolean verifyWasInstanceInitializer() {
     VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
-    HorizontallyMergedClasses horizontallyMergedClasses = appView.horizontallyMergedClasses();
     assert verticallyMergedClasses != null;
-    assert horizontallyMergedClasses != null;
     assert verticallyMergedClasses.isMergeTarget(method.getHolderType())
-        || horizontallyMergedClasses.isMergeTarget(method.getHolderType());
+        || appView.horizontallyMergedClasses().isMergeTarget(method.getHolderType());
     assert appView
         .dexItemFactory()
         .isConstructor(appView.graphLens().getOriginalMethodSignature(method.getReference()));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index f7b79aa..c3c2307 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -300,6 +300,12 @@
     if (clazz.classInitializationMayHaveSideEffects(appView)) {
       return EligibilityStatus.NOT_ELIGIBLE;
     }
+
+    if (!appView.testing().allowClassInliningOfSynthetics
+        && appView.getSyntheticItems().isSyntheticClass(clazz)) {
+      return EligibilityStatus.NOT_ELIGIBLE;
+    }
+
     return EligibilityStatus.ELIGIBLE;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 69c27c5..09fee09 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.InstantiatedSubTypeInfo;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
@@ -1094,7 +1095,26 @@
         prunedTypes,
         lens.rewriteFieldKeys(switchMaps),
         lens.rewriteTypes(lockCandidates),
-        lens.rewriteTypeKeys(initClassReferences));
+        rewriteInitClassReferences(lens));
+  }
+
+  public Map<DexType, Visibility> rewriteInitClassReferences(GraphLens lens) {
+    return lens.rewriteTypeKeys(
+        initClassReferences,
+        (minimumRequiredVisibilityForCurrentMethod,
+            otherMinimumRequiredVisibilityForCurrentMethod) -> {
+          assert !minimumRequiredVisibilityForCurrentMethod.isPrivate();
+          assert !otherMinimumRequiredVisibilityForCurrentMethod.isPrivate();
+          if (minimumRequiredVisibilityForCurrentMethod.isPublic()
+              || otherMinimumRequiredVisibilityForCurrentMethod.isPublic()) {
+            return Visibility.PUBLIC;
+          }
+          if (minimumRequiredVisibilityForCurrentMethod.isProtected()
+              || otherMinimumRequiredVisibilityForCurrentMethod.isProtected()) {
+            return Visibility.PROTECTED;
+          }
+          return Visibility.PACKAGE_PRIVATE;
+        });
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 17b5a8f..a1f9b48 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -232,12 +232,10 @@
   }
 
   public boolean isEligibleForClassMerging(DexProgramClass clazz, HorizontalClassMerger.Mode mode) {
-    // TODO(b/187496738): Allow merging all synthetics in the final round of class merging.
     assert isSyntheticClass(clazz);
-    return isSyntheticLambda(clazz);
+    return mode.isFinal() || isSyntheticLambda(clazz);
   }
 
-  // TODO(b/186211926): Allow merging of legacy synthetics.
   private boolean isSyntheticLambda(DexProgramClass clazz) {
     if (!isNonLegacySynthetic(clazz)) {
       return false;
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 f9580b6..20d0405 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1212,6 +1212,7 @@
         !Version.isDevelopmentVersion()
             || System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null;
     private boolean enableInterfaceMerging = false;
+    private boolean enableSyntheticMerging = true;
     private boolean ignoreRuntimeTypeChecksForTesting = false;
     private boolean restrictToSynthetics = false;
 
@@ -1221,6 +1222,10 @@
       enable = false;
     }
 
+    public void disableSyntheticMerging() {
+      enableSyntheticMerging = false;
+    }
+
     public void enable() {
       enable = true;
     }
@@ -1249,8 +1254,7 @@
         return enableInlining && isShrinking();
       }
       assert mode.isFinal();
-      // TODO(b/187496738): Enable final class merging.
-      return false;
+      return true;
     }
 
     public boolean isIgnoreRuntimeTypeChecksForTestingEnabled() {
@@ -1258,13 +1262,16 @@
     }
 
     public boolean isInterfaceMergingEnabled() {
-      return isInterfaceMergingEnabled(HorizontalClassMerger.Mode.INITIAL)
-          || isInterfaceMergingEnabled(HorizontalClassMerger.Mode.FINAL);
+      assert !isInterfaceMergingEnabled(HorizontalClassMerger.Mode.INITIAL);
+      return isInterfaceMergingEnabled(HorizontalClassMerger.Mode.FINAL);
+    }
+
+    public boolean isSyntheticMergingEnabled() {
+      return enableSyntheticMerging;
     }
 
     public boolean isInterfaceMergingEnabled(HorizontalClassMerger.Mode mode) {
-      // TODO(b/187496738): Only run interface merging during final class merging.
-      return enableInterfaceMerging;
+      return enableInterfaceMerging && mode.isFinal();
     }
 
     public boolean isRestrictedToSynthetics() {
@@ -1367,7 +1374,9 @@
     public boolean addCallEdgesForLibraryInvokes = false;
 
     public boolean allowCheckDiscardedErrors = false;
+    public boolean allowClassInliningOfSynthetics = true;
     public boolean allowInjectedAnnotationMethods = false;
+    public boolean allowInliningOfSynthetics = true;
     public boolean allowTypeErrors =
         !Version.isDevelopmentVersion()
             || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 97cf033..d78af9d 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -444,6 +444,11 @@
     return self();
   }
 
+  public T noClassInliningOfSynthetics() {
+    return addOptionsModification(
+        options -> options.testing.allowClassInliningOfSynthetics = false);
+  }
+
   public T noClassStaticizing() {
     return noClassStaticizing(true);
   }
@@ -467,8 +472,21 @@
   }
 
   public T noHorizontalClassMerging(Class<?> clazz) {
-    return addKeepRules(
-        "-" + NoHorizontalClassMergingRule.RULE_NAME + " class " + clazz.getTypeName());
+    return noHorizontalClassMerging(clazz.getTypeName());
+  }
+
+  public T noHorizontalClassMerging(String typeName) {
+    return addKeepRules("-" + NoHorizontalClassMergingRule.RULE_NAME + " class " + typeName)
+        .enableProguardTestOptions();
+  }
+
+  public T noHorizontalClassMergingOfSynthetics() {
+    return addOptionsModification(
+        options -> options.horizontalClassMergerOptions().disableSyntheticMerging());
+  }
+
+  public T noInliningOfSynthetics() {
+    return addOptionsModification(options -> options.testing.allowInliningOfSynthetics = false);
   }
 
   public T enableNoUnusedInterfaceRemovalAnnotations() {
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 121e7c4..40bbfd4 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,7 +10,6 @@
 
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.debug.DebugTestConfig;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -438,7 +437,7 @@
   }
 
   public T enableCoreLibraryDesugaring(
-      AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+      AndroidApiLevel minApiLevel, StringConsumer keepRuleConsumer) {
     return enableCoreLibraryDesugaring(
         minApiLevel,
         keepRuleConsumer,
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
index 5484fd8..817c786 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
@@ -30,6 +30,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -37,27 +39,25 @@
         .assertSuccessWithOutputLines("42", "13", "21", "39", "print a", "print b")
         .inspect(
             codeInspector -> {
-                ClassSubject aClassSubject = codeInspector.clazz(A.class);
-                assertThat(aClassSubject, isPresent());
-                FieldSubject classIdFieldSubject =
-                    aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
-                assertThat(classIdFieldSubject, isPresent());
+              ClassSubject aClassSubject = codeInspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              FieldSubject classIdFieldSubject =
+                  aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
+              assertThat(classIdFieldSubject, isPresent());
 
-                MethodSubject firstInitSubject = aClassSubject.init("int");
-                assertThat(firstInitSubject, isPresent());
-                assertThat(
-                    firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
+              MethodSubject firstInitSubject = aClassSubject.init("int");
+              assertThat(firstInitSubject, isPresent());
+              assertThat(firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject otherInitSubject = aClassSubject.init("long", "int");
-                assertThat(otherInitSubject, isPresent());
-                assertThat(
-                    otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
+              MethodSubject otherInitSubject = aClassSubject.init("long", "int");
+              assertThat(otherInitSubject, isPresent());
+              assertThat(otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
-                assertThat(printSubject, isPresent());
-                assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
+              MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
+              assertThat(printSubject, isPresent());
+              assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
-                assertThat(codeInspector.clazz(B.class), not(isPresent()));
+              assertThat(codeInspector.clazz(B.class), not(isPresent()));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
index d74ae0a..580598b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import org.junit.Test;
 
 public class OverrideDefaultMethodTest extends HorizontalClassMergingTestBase {
@@ -30,7 +30,17 @@
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addHorizontallyMergedClassesInspector(
-            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+            inspector -> {
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                inspector.assertNoClassesMerged();
+              } else {
+                inspector
+                    .assertClassesNotMerged(A.class, B.class)
+                    .assertIsCompleteMergeGroup(
+                        SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
+                        SyntheticItemsTestUtils.syntheticCompanionClass(J.class));
+              }
+            })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("I", "B", "J")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
index 95dc561..65ca772 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
@@ -13,7 +13,11 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.A;
+import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.B;
+import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.I;
+import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.J;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import org.junit.Test;
 
 public class OverrideDefaultOnSuperMethodTest extends HorizontalClassMergingTestBase {
@@ -32,7 +36,17 @@
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addHorizontallyMergedClassesInspector(
-            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+            inspector -> {
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                inspector.assertNoClassesMerged();
+              } else {
+                inspector
+                    .assertClassesNotMerged(A.class, B.class)
+                    .assertIsCompleteMergeGroup(
+                        SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
+                        SyntheticItemsTestUtils.syntheticCompanionClass(J.class));
+              }
+            })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("I", "B", "J")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
index 097be15..1c5d915 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
@@ -37,6 +37,7 @@
     this.parameters = parameters;
   }
 
+  // TODO(b/173990042): Disallow merging of A and B in the first round of class merging.
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
@@ -46,12 +47,20 @@
         // contributes the default method K.m() to A, and the merging of J into I would contribute
         // the default method J.m() to A.
         .addHorizontallyMergedClassesInspector(
-            inspector ->
+            inspector -> {
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                inspector
+                    .assertIsCompleteMergeGroup(A.class, B.class)
+                    .assertMergedInto(B.class, A.class)
+                    .assertClassesNotMerged(I.class, J.class, K.class);
+              } else {
                 inspector
                     .assertIsCompleteMergeGroup(A.class, B.class)
                     .assertMergedInto(B.class, A.class)
                     .assertIsCompleteMergeGroup(I.class, J.class)
-                    .assertClassesNotMerged(K.class))
+                    .assertClassesNotMerged(K.class);
+              }
+            })
         .addOptionsModification(
             options -> {
               assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
@@ -73,15 +82,16 @@
 
               ClassSubject bClassSubject = inspector.clazz(C.class);
               assertThat(bClassSubject, isPresent());
-              assertThat(bClassSubject, isImplementing(inspector.clazz(I.class)));
+              assertThat(
+                  bClassSubject,
+                  isImplementing(
+                      inspector.clazz(
+                          parameters.canUseDefaultAndStaticInterfaceMethods()
+                              ? J.class
+                              : I.class)));
             })
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/173990042): Should succeed with "A", "K", "J".
-        .applyIf(
-            parameters.isCfRuntime() || parameters.canUseDefaultAndStaticInterfaceMethods(),
-            builder -> builder.assertSuccessWithOutputLines("A", "J", "J"),
-            builder ->
-                builder.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class));
+        .assertSuccessWithOutputLines("A", "K", "J");
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
index e6ea690..d4f14bd 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -45,7 +44,13 @@
         // I and J are not eligible for merging, since class A (implements I) inherits a default m()
         // method from K, which is also on J.
         .addHorizontallyMergedClassesInspector(
-            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+            inspector -> {
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                inspector.assertNoClassesMerged();
+              } else {
+                inspector.assertIsCompleteMergeGroup(I.class, J.class);
+              }
+            })
         .addOptionsModification(
             options -> {
               assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
@@ -67,7 +72,13 @@
 
               ClassSubject bClassSubject = inspector.clazz(B.class);
               assertThat(bClassSubject, isPresent());
-              assertThat(bClassSubject, isImplementing(inspector.clazz(J.class)));
+              assertThat(
+                  bClassSubject,
+                  isImplementing(
+                      inspector.clazz(
+                          parameters.canUseDefaultAndStaticInterfaceMethods()
+                              ? J.class
+                              : I.class)));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("K", "J");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
index 979d57a..f9e85ba 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -78,7 +79,11 @@
         // TODO(b/173990042): Should succeed with "K", "J".
         .applyIf(
             parameters.isCfRuntime(),
-            builder -> builder.assertSuccessWithOutputLines("J", "J"),
+            builder ->
+                builder.assertFailureWithErrorThatThrows(
+                    parameters.isCfRuntime(CfVm.JDK11)
+                        ? AbstractMethodError.class
+                        : IncompatibleClassChangeError.class),
             builder -> builder.assertSuccessWithOutputLines("K", "J"));
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
index 384edae..7ca9cc5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
@@ -43,6 +43,9 @@
             })
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
+        .noClassInliningOfSynthetics()
+        .noHorizontalClassMergingOfSynthetics()
+        .noInliningOfSynthetics()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
index 0aa2fba..dad75a4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
@@ -34,9 +34,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        // TODO(b/173990042): We should be able to merge I and J.
         .addHorizontallyMergedClassesInspector(
-            inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+            inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
         .addOptionsModification(
             options -> {
               assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
@@ -44,6 +43,9 @@
             })
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
+        .noClassInliningOfSynthetics()
+        .noHorizontalClassMergingOfSynthetics()
+        .noInliningOfSynthetics()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
@@ -53,7 +55,7 @@
   static class Main {
 
     public static void main(String[] args) {
-      Object o = (I & J) () -> "I & J";
+      Object o = (I & J) () -> System.currentTimeMillis() > 0 ? "I & J" : null;
       System.out.println(((I) o).f());
       System.out.println(((J) o).f());
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
index 97a279d..e58ca16 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
@@ -45,6 +45,9 @@
             })
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
+        .noClassInliningOfSynthetics()
+        .noHorizontalClassMergingOfSynthetics()
+        .noInliningOfSynthetics()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
index 2b61fce..a877121 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
@@ -45,6 +45,9 @@
             })
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
+        .noClassInliningOfSynthetics()
+        .noHorizontalClassMergingOfSynthetics()
+        .noInliningOfSynthetics()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
@@ -54,8 +57,8 @@
   static class Main {
 
     public static void main(String[] args) {
-      System.out.println(((I) () -> "I").f());
-      System.out.println(((J) () -> "J").f());
+      System.out.println(((I) () -> System.currentTimeMillis() > 0 ? "I" : null).f());
+      System.out.println(((J) () -> System.currentTimeMillis() > 0 ? "J" : null).f());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
index 9fe5cbb..2d264db 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
@@ -43,6 +43,9 @@
             })
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
+        .noClassInliningOfSynthetics()
+        .noHorizontalClassMergingOfSynthetics()
+        .noInliningOfSynthetics()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
index 4fa557e..23ed1ea 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
@@ -34,9 +34,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        // TODO(b/173990042): I and J should be merged.
         .addHorizontallyMergedClassesInspector(
-            inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+            inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
         .addOptionsModification(
             options -> {
               assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
@@ -44,6 +43,8 @@
             })
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
+        .noClassInliningOfSynthetics()
+        .noInliningOfSynthetics()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
index 1773d26..6ad6ef1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -44,7 +43,13 @@
         .addKeepMainRule(Main.class)
         // I and J are not eligible for merging, since they declare the same default method.
         .addHorizontallyMergedClassesInspector(
-            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+            inspector -> {
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                inspector.assertNoClassesMerged();
+              } else {
+                inspector.assertIsCompleteMergeGroup(I.class, J.class);
+              }
+            })
         .addOptionsModification(
             options -> {
               assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
@@ -65,7 +70,13 @@
 
               ClassSubject bClassSubject = inspector.clazz(B.class);
               assertThat(bClassSubject, isPresent());
-              assertThat(bClassSubject, isImplementing(inspector.clazz(J.class)));
+              assertThat(
+                  bClassSubject,
+                  isImplementing(
+                      inspector.clazz(
+                          parameters.canUseDefaultAndStaticInterfaceMethods()
+                              ? J.class
+                              : I.class)));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("I", "J");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
index 53f5ae2..1c33206 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.classmerging.horizontalstatic;
 
 import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticCompanionClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -12,9 +13,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -37,32 +37,41 @@
   @Test
   public void test() throws Exception {
     String expectedOutput = StringUtils.lines("In I.a()", "In J.b()", "In A.c()");
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        // TODO(b/173990042): Extend horizontal class merging to interfaces.
+        .addHorizontallyMergedClassesInspector(
+            inspector -> {
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                inspector.assertNoClassesMerged();
+              } else {
+                inspector
+                    .assertClassReferencesMerged(
+                        SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
+                        SyntheticItemsTestUtils.syntheticCompanionClass(J.class))
+                    .assertClassesNotMerged(I.class, J.class);
+              }
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(expectedOutput)
+        .inspect(
+            inspector -> {
+              // We do not allow horizontal class merging of interfaces and classes. Therefore, A
+              // should remain in the output.
+              assertThat(inspector.clazz(A.class), isPresent());
 
-    CodeInspector inspector =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(getClass())
-            .addKeepMainRule(TestClass.class)
-            // TODO(b/173990042): Extend horizontal class merging to interfaces.
-            .addHorizontallyMergedClassesInspector(
-                HorizontallyMergedClassesInspector::assertNoClassesMerged)
-            .enableInliningAnnotations()
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), TestClass.class)
-            .assertSuccessWithOutput(expectedOutput)
-            .inspector();
-
-    // We do not allow horizontal class merging of interfaces and classes. Therefore, A should
-    // remain in the output.
-    assertThat(inspector.clazz(A.class), isPresent());
-
-    // TODO(b/173990042): I and J should be merged.
-    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
-      assertThat(inspector.clazz(I.class), isPresent());
-      assertThat(inspector.clazz(J.class), isPresent());
-    } else {
-      assertThat(inspector.clazz(syntheticCompanionClass(I.class)), isPresent());
-      assertThat(inspector.clazz(syntheticCompanionClass(I.class)), isPresent());
-    }
+              // TODO(b/173990042): I and J should be merged.
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                assertThat(inspector.clazz(I.class), isPresent());
+                assertThat(inspector.clazz(J.class), isPresent());
+              } else {
+                assertThat(inspector.clazz(syntheticCompanionClass(I.class)), isPresent());
+                assertThat(inspector.clazz(syntheticCompanionClass(J.class)), isAbsent());
+              }
+            });
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
index 993b56f..2d396f7d 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
@@ -73,6 +73,7 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepAllClassesRule()
         .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .noHorizontalClassMergingOfSynthetics()
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
@@ -125,7 +126,7 @@
     static Callable<Class<?>> staticOuter() {
       return new Callable<Class<?>>() {
         @Override
-        public Class<?> call() throws Exception {
+        public Class<?> call() {
           return getClass().getEnclosingClass();
         }
       };
@@ -134,7 +135,7 @@
     default Callable<Class<?>> defaultOuter() {
       return new Callable<Class<?>>() {
         @Override
-        public Class<?> call() throws Exception {
+        public Class<?> call() {
           return getClass().getEnclosingClass();
         }
       };
@@ -145,7 +146,7 @@
     static Callable<Class<?>> staticOuter() {
       class Local implements Callable<Class<?>> {
         @Override
-        public Class<?> call() throws Exception {
+        public Class<?> call() {
           return getClass().getEnclosingClass();
         }
       }
@@ -155,7 +156,7 @@
     default Callable<Class<?>> defaultOuter() {
       class Local implements Callable<Class<?>> {
         @Override
-        public Class<?> call() throws Exception {
+        public Class<?> call() {
           return getClass().getEnclosingClass();
         }
       }
diff --git a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
index aac4a9e..1f15b61 100644
--- a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -102,9 +103,8 @@
               // R8 will optimize the generated methods for the two cases below where the thrown
               // exception is known or not, thus the synthetic methods will be 2.
               int expectedSynthetics =
-                  parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
-                      ? 2
-                      : 0;
+                  BooleanUtils.intValue(
+                      parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport()));
               List<FoundClassSubject> foundClassSubjects = inspector.allClasses();
               assertEquals(INPUT_CLASSES + expectedSynthetics, foundClassSubjects.size());
             });
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
index a86ee63..0531857 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
@@ -49,8 +50,11 @@
               options.outline.threshold = 2;
               options.outline.minSize = 2;
             })
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .noHorizontalClassMergingOfSynthetics()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
index dd6dace..0d1f637 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b149971007/B149971007.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
@@ -25,7 +26,6 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,18 +66,23 @@
     return false;
   }
 
-  private void checkOutlineFromFeature(CodeInspector inspector) {
-    // There are two expected outlines, each is currently in its own class.
-    List<FoundMethodSubject> allMethods = new ArrayList<>();
-    for (int i = 0; i < 2; i++) {
-      ClassSubject clazz =
-          inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, i));
-      assertThat(clazz, isPresent());
-      assertEquals(1, clazz.allMethods().size());
-      allMethods.addAll(clazz.allMethods());
-    }
+  private ClassSubject checkOutlineFromFeature(CodeInspector inspector) {
+    // There are two expected outlines, in a single single class after horizontal class merging.
+    ClassSubject classSubject0 =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 0));
+    ClassSubject classSubject1 =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 1));
+    assertThat(classSubject1, notIf(isPresent(), classSubject0.isPresent()));
+
+    ClassSubject classSubject = classSubject0.isPresent() ? classSubject0 : classSubject1;
+
+    List<FoundMethodSubject> allMethods = classSubject.allMethods();
+    assertEquals(2, allMethods.size());
+
     // One of the methods is StringBuilder the other references the feature.
     assertTrue(allMethods.stream().anyMatch(this::referenceFeatureClass));
+
+    return classSubject;
   }
 
   @Test
@@ -89,33 +94,24 @@
             .addKeepClassAndMembersRules(FeatureClass.class)
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(options -> options.outline.threshold = 2)
-            .compile()
-            .inspect(this::checkOutlineFromFeature);
+            .compile();
+
+    CodeInspector inspector = compileResult.inspector();
+    ClassSubject outlineClass = checkOutlineFromFeature(inspector);
 
     // Check that parts of method1, ..., method4 in FeatureClass was outlined.
-    ClassSubject featureClass = compileResult.inspector().clazz(FeatureClass.class);
+    ClassSubject featureClass = inspector.clazz(FeatureClass.class);
     assertThat(featureClass, isPresent());
 
     // Find the final names of the two outline classes.
-    String outlineClassName0 =
-        ClassNameMapper.mapperFromString(compileResult.getProguardMap())
-            .getObfuscatedToOriginalMapping()
-            .inverse
-            .get(
-                SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 0).getTypeName());
-    String outlineClassName1 =
-        ClassNameMapper.mapperFromString(compileResult.getProguardMap())
-            .getObfuscatedToOriginalMapping()
-            .inverse
-            .get(
-                SyntheticItemsTestUtils.syntheticOutlineClass(FeatureClass.class, 1).getTypeName());
+    String outlineClassName = outlineClass.getFinalName();
 
     // Verify they are called from the feature methods.
     // Note: should the choice of synthetic grouping change these expectations will too.
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName0));
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName0));
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName1));
-    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method4"), outlineClassName1));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method1"), outlineClassName));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method2"), outlineClassName));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method3"), outlineClassName));
+    assertTrue(invokesOutline(featureClass.uniqueMethodWithName("method4"), outlineClassName));
 
     compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput("123456");
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index d268888..5a60806 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -14,7 +14,6 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.R8TestRunResult;
-import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -117,6 +116,7 @@
         .addKeepAttributes("InnerClasses", "EnclosingMethod")
         .addOptionsModification(this::configure)
         .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), main)
         .assertSuccessWithOutput(EXPECTED);
@@ -124,10 +124,11 @@
 
   @Test
   public void testTrivial() throws Exception {
-    SingleTestRunResult result =
+    R8TestRunResult result =
         testForR8(parameters.getBackend())
             .addProgramClasses(classes)
             .enableInliningAnnotations()
+            .enableNoHorizontalClassMergingAnnotations()
             .addKeepMainRule(main)
             .noMinification()
             .addKeepAttributes("InnerClasses", "EnclosingMethod")
@@ -241,7 +242,7 @@
         HostOkFieldOnly.class,
         CandidateOkFieldOnly.class
     };
-    SingleTestRunResult result =
+    R8TestRunResult result =
         testForR8(parameters.getBackend())
             .addProgramClasses(classes)
             .enableInliningAnnotations()
@@ -283,6 +284,7 @@
             .addProgramClasses(classes)
             .enableInliningAnnotations()
             .enableNoHorizontalClassMergingAnnotations()
+            .enableNoHorizontalClassMergingAnnotations()
             .enableMemberValuePropagationAnnotations()
             .addKeepMainRule(main)
             .allowAccessModification()
@@ -405,7 +407,7 @@
         Candidate.class
     };
     String javaOutput = runOnJava(main);
-    SingleTestRunResult result =
+    R8TestRunResult result =
         testForR8(parameters.getBackend())
             .addProgramClasses(classes)
             .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
index 4e3f691..fe09d69 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -45,6 +46,7 @@
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Companion#boo", "Companion#foo")
@@ -79,6 +81,7 @@
   static class Host {
     private static final Companion companion = new Companion();
 
+    @NoHorizontalClassMerging
     static class Companion {
       @NeverInline
       private static Object boo() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
index 295657a..8c0b01f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
@@ -6,7 +6,9 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoHorizontalClassMerging;
 
+@NoHorizontalClassMerging
 public class CandidateConflictField {
 
   @NeverPropagateValue
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
index 6bc91b4..a743f11 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.ir.optimize.staticizer.trivial;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 
+@NoHorizontalClassMerging
 public class SimpleWithGetter {
   private static SimpleWithGetter INSTANCE = new SimpleWithGetter();
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
index 24edea4..b02dc73 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.ir.optimize.staticizer.trivial;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 
+@NoHorizontalClassMerging
 public class SimpleWithParams {
   static SimpleWithParams INSTANCE = new SimpleWithParams(123);
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithPhi.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithPhi.java
index f3c76cb..24786f4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithPhi.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithPhi.java
@@ -4,7 +4,9 @@
 package com.android.tools.r8.ir.optimize.staticizer.trivial;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 
+@NoHorizontalClassMerging
 public class SimpleWithPhi {
   public static class Companion {
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.java
index f7e50d9..abb4c9f 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/ConditionalKeepClassMethodsAllowShrinkingCompatibilityTest.java
@@ -9,6 +9,7 @@
 import static org.hamcrest.CoreMatchers.not;
 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.TestShrinkerBuilder;
@@ -61,9 +62,12 @@
   @Test
   public void test() throws Exception {
     if (shrinker.isR8()) {
-      run(testForR8(parameters.getBackend()));
+      run(testForR8(parameters.getBackend()).enableNoHorizontalClassMergingAnnotations());
     } else {
-      run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
+      run(
+          testForProguard(shrinker.getProguardVersion())
+              .addDontWarn(getClass())
+              .addNoHorizontalClassMergingAnnotations());
     }
   }
 
@@ -113,6 +117,7 @@
     }
   }
 
+  @NoHorizontalClassMerging
   static class B {
     public String foo() {
       return "B::foo";
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
index c9b3bc7..f198845 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepAllowShrinkingCompatibilityTest.java
@@ -9,6 +9,7 @@
 import static org.hamcrest.CoreMatchers.not;
 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.TestShrinkerBuilder;
@@ -64,9 +65,13 @@
       run(
           testForR8(parameters.getBackend())
               // Allowing all of shrinking, optimization and obfuscation will amount to a nop rule.
-              .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation));
+              .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation)
+              .enableNoHorizontalClassMergingAnnotations());
     } else {
-      run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
+      run(
+          testForProguard(shrinker.getProguardVersion())
+              .addDontWarn(getClass())
+              .addNoHorizontalClassMergingAnnotations());
     }
   }
 
@@ -107,6 +112,7 @@
     }
   }
 
+  @NoHorizontalClassMerging
   static class B {
     public String foo() {
       return "B::foo";
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
index e7d5191..5a1d0f9 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassMethodsAllowShrinkingCompatibilityTest.java
@@ -9,6 +9,7 @@
 import static org.hamcrest.CoreMatchers.not;
 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.TestShrinkerBuilder;
@@ -64,9 +65,13 @@
       run(
           testForR8(parameters.getBackend())
               // Allowing all of shrinking, optimization and obfuscation will amount to a nop rule.
-              .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation));
+              .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation)
+              .enableNoHorizontalClassMergingAnnotations());
     } else {
-      run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
+      run(
+          testForProguard(shrinker.getProguardVersion())
+              .addDontWarn(getClass())
+              .addNoHorizontalClassMergingAnnotations());
     }
   }
 
@@ -116,6 +121,7 @@
     }
   }
 
+  @NoHorizontalClassMerging
   static class B {
     public String foo() {
       return "B::foo";
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 ae22c5e..0410bc0 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
@@ -106,7 +106,14 @@
   }
 
   public HorizontallyMergedClassesInspector assertNoClassesMerged() {
-    assertTrue(horizontallyMergedClasses.getSources().isEmpty());
+    if (!horizontallyMergedClasses.getSources().isEmpty()) {
+      DexType source = horizontallyMergedClasses.getSources().iterator().next();
+      fail(
+          "Expected no classes to be merged, got: "
+              + source.getTypeName()
+              + " -> "
+              + getTarget(source).getTypeName());
+    }
     return this;
   }