Merge commit '961e3fb4895d6759fe87a26bf579373777508f3c' into dev-release
diff --git a/build.gradle b/build.gradle
index b7c7830..99676ef 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1006,9 +1006,7 @@
 task R8Lib {
     dependsOn r8LibCreateTask(
             "Main",
-            ["src/main/keep.txt",
-             "src/main/keep-applymapping.txt",
-             generateR8LibKeepRules.outputs.files[0]],
+            ["src/main/keep.txt", generateR8LibKeepRules.outputs.files[0]],
             r8NoManifestWithRelocatedDeps,
             r8LibPath,
     ).dependsOn(generateR8LibKeepRules)
@@ -1019,7 +1017,7 @@
 task R8LibNoDeps {
     dependsOn r8LibCreateTask(
             "NoDeps",
-            ["src/main/keep.txt", "src/main/keep-applymapping.txt"],
+            ["src/main/keep.txt"],
             r8NoManifestWithoutDeps,
             r8LibExludeDepsPath,
             "--release",
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 16d3642..92d9c12 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -410,19 +410,16 @@
 
           // Recompute the subtyping information.
           Set<DexType> removedClasses = pruner.getRemovedClasses();
-          appView.setAppInfo(
-              appView
-                  .appInfo()
-                  .withLiveness()
-                  .prunedCopyFrom(
-                      prunedApp,
-                      removedClasses,
-                      pruner.getMethodsToKeepForConfigurationDebugging()));
-          appView.setAppServices(appView.appServices().prunedCopy(removedClasses));
+          appView.removePrunedClasses(
+              prunedApp, removedClasses, pruner.getMethodsToKeepForConfigurationDebugging());
           new AbstractMethodRemover(
                   appViewWithLiveness, appViewWithLiveness.appInfo().computeSubtypingInfo())
               .run();
 
+          if (appView.options().protoShrinking().isProtoShrinkingEnabled()) {
+            appView.protoShrinker().enumProtoShrinker.clearDeadEnumLiteMaps();
+          }
+
           AnnotationRemover annotationRemover =
               annotationRemoverBuilder
                   .computeClassesToRetainInnerClassAttributeFor(appViewWithLiveness)
@@ -571,7 +568,6 @@
               appView.appInfo().app().asDirect().builder();
           HorizontalClassMergerGraphLens lens = merger.run(appBuilder);
           if (lens != null) {
-            appView.setHorizontallyMergedClasses(lens.getHorizontallyMergedClasses());
             appView.rewriteWithLensAndApplication(lens, appBuilder.build());
           }
           timing.end();
@@ -592,8 +588,6 @@
         appViewWithLiveness.setAppInfo(new EnumValueInfoMapCollector(appViewWithLiveness).run());
       }
 
-      appView.setAppServices(appView.appServices().rewrittenWithLens(appView.graphLens()));
-
       // Collect the already pruned types before creating a new app info without liveness.
       // TODO: we should avoid removing liveness.
       Set<DexType> prunedTypes = appView.withLiveness().appInfo().getPrunedTypes();
@@ -731,14 +725,11 @@
               ExceptionUtils.withFinishedResourceHandler(
                   options.reporter, options.usageInformationConsumer);
             }
-            appViewWithLiveness.setAppInfo(
-                appViewWithLiveness
-                    .appInfo()
-                    .prunedCopyFrom(
-                        application,
-                        CollectionUtils.mergeSets(prunedTypes, removedClasses),
-                        pruner.getMethodsToKeepForConfigurationDebugging()));
-            appView.setAppServices(appView.appServices().prunedCopy(removedClasses));
+
+            appView.removePrunedClasses(
+                application,
+                CollectionUtils.mergeSets(prunedTypes, removedClasses),
+                pruner.getMethodsToKeepForConfigurationDebugging());
 
             new BridgeHoisting(appViewWithLiveness).run();
 
@@ -779,6 +770,8 @@
         }
 
         if (appView.options().protoShrinking().isProtoShrinkingEnabled()) {
+          appView.protoShrinker().enumProtoShrinker.verifyDeadEnumLiteMapsAreDead();
+
           IRConverter converter = new IRConverter(appView, timing, null, mainDexTracingResult);
 
           // If proto shrinking is enabled, we need to reprocess every dynamicMethod(). This ensures
@@ -814,8 +807,7 @@
       appView.setGraphLens(memberRebindingLens);
 
       // Perform repackaging.
-      // TODO(b/165783399): Consider making repacking available without minification.
-      if (options.isMinifying() && options.testing.enableExperimentalRepackaging) {
+      if (options.isRepackagingEnabled() && options.testing.enableExperimentalRepackaging) {
         DirectMappedDexApplication.Builder appBuilder =
             appView.appInfo().app().asDirect().builder();
         // TODO(b/165783399): We need to deal with non-rebound member references in the writer,
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index de7599a..d9ad820 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
@@ -93,12 +94,12 @@
     }
     // Check if service is defined feature
     DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
-    if (classToFeatureSplitMap.isInFeature(serviceClass)) {
+    if (serviceClass != null && classToFeatureSplitMap.isInFeature(serviceClass)) {
       return true;
     }
-    for (DexType dexType : featureImplementations.get(FeatureSplit.BASE)) {
-      DexProgramClass implementationClass = appView.definitionForProgramType(dexType);
-      if (classToFeatureSplitMap.isInFeature(implementationClass)) {
+    for (DexType implementationType : featureImplementations.get(FeatureSplit.BASE)) {
+      DexProgramClass implementationClass = appView.definitionForProgramType(implementationType);
+      if (implementationClass != null && classToFeatureSplitMap.isInFeature(implementationClass)) {
         return true;
       }
     }
@@ -187,19 +188,20 @@
   public static class Builder {
 
     private final AppView<?> appView;
+    private final InternalOptions options;
     private final Map<DexType, Map<FeatureSplit, List<DexType>>> services = new LinkedHashMap<>();
 
     private Builder(AppView<?> appView) {
       this.appView = appView;
+      this.options = appView.options();
     }
 
     public AppServices build() {
       for (DataResourceProvider provider : appView.appInfo().app().dataResourceProviders) {
         readServices(provider, FeatureSplit.BASE);
       }
-      if (appView.options().featureSplitConfiguration != null) {
-        List<FeatureSplit> featureSplits =
-            appView.options().featureSplitConfiguration.getFeatureSplits();
+      if (options.featureSplitConfiguration != null) {
+        List<FeatureSplit> featureSplits = options.featureSplitConfiguration.getFeatureSplits();
         for (FeatureSplit featureSplit : featureSplits) {
           for (ProgramResourceProvider provider : featureSplit.getProgramResourceProviders()) {
             DataResourceProvider dataResourceProvider = provider.getDataResourceProvider();
@@ -238,28 +240,45 @@
       public void visit(DataEntryResource file) {
         try {
           String name = file.getName();
-          if (name.startsWith(SERVICE_DIRECTORY_NAME)) {
-            String serviceName = name.substring(SERVICE_DIRECTORY_NAME.length());
-            if (DescriptorUtils.isValidJavaType(serviceName)) {
-              String serviceDescriptor = DescriptorUtils.javaTypeToDescriptor(serviceName);
-              DexType serviceType = appView.dexItemFactory().createType(serviceDescriptor);
-              byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
-              String contents = new String(bytes, Charset.defaultCharset());
-              Map<FeatureSplit, List<DexType>> featureSplitImplementations =
-                  services.computeIfAbsent(serviceType, k -> new LinkedHashMap<>());
-              List<DexType> serviceImplementations =
-                  featureSplitImplementations.computeIfAbsent(featureSplit, f -> new ArrayList<>());
-              readServiceImplementationsForService(
-                  contents, file.getOrigin(), serviceImplementations);
+          if (!name.startsWith(SERVICE_DIRECTORY_NAME)) {
+            return;
+          }
+          String serviceName = name.substring(SERVICE_DIRECTORY_NAME.length());
+          if (!DescriptorUtils.isValidJavaType(serviceName)) {
+            return;
+          }
+          String serviceDescriptor = DescriptorUtils.javaTypeToDescriptor(serviceName);
+          DexType serviceType = appView.dexItemFactory().createType(serviceDescriptor);
+          if (appView.enableWholeProgramOptimizations()) {
+            DexClass serviceClass =
+                appView.appInfo().definitionForWithoutExistenceAssert(serviceType);
+            if (serviceClass == null) {
+              warn(
+                  "Unexpected reference to missing service class: META-INF/services/"
+                      + serviceType.toSourceString()
+                      + ".",
+                  serviceType,
+                  file.getOrigin());
             }
           }
+          byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+          String contents = new String(bytes, Charset.defaultCharset());
+          Map<FeatureSplit, List<DexType>> featureSplitImplementations =
+              services.computeIfAbsent(serviceType, k -> new LinkedHashMap<>());
+          List<DexType> serviceImplementations =
+              featureSplitImplementations.computeIfAbsent(featureSplit, f -> new ArrayList<>());
+          readServiceImplementationsForService(
+              contents, file.getOrigin(), serviceType, serviceImplementations);
         } catch (IOException | ResourceException e) {
           throw new CompilationError(e.getMessage(), e);
         }
       }
 
       private void readServiceImplementationsForService(
-          String contents, Origin origin, List<DexType> serviceImplementations) {
+          String contents,
+          Origin origin,
+          DexType serviceType,
+          List<DexType> serviceImplementations) {
         if (contents != null) {
           StringUtils.splitLines(contents).stream()
               .map(String::trim)
@@ -272,17 +291,33 @@
                   serviceImplementationType -> {
                     if (!serviceImplementationType.isClassType()) {
                       // Should never happen.
-                      appView
-                          .options()
-                          .reporter
-                          .warning(
-                              new StringDiagnostic(
-                                  "Unexpected service implementation found in META-INF/services/: `"
-                                      + serviceImplementationType.toSourceString()
-                                      + "`.",
-                                  origin));
+                      warn(
+                          "Unexpected service implementation found in META-INF/services/"
+                              + serviceType.toSourceString()
+                              + ": "
+                              + serviceImplementationType.toSourceString()
+                              + ".",
+                          serviceImplementationType,
+                          origin);
                       return false;
                     }
+                    if (appView.enableWholeProgramOptimizations()) {
+                      DexClass serviceImplementationClass =
+                          appView
+                              .appInfo()
+                              .definitionForWithoutExistenceAssert(serviceImplementationType);
+                      if (serviceImplementationClass == null) {
+                        warn(
+                            "Unexpected reference to missing service implementation class in "
+                                + "META-INF/services/"
+                                + serviceType.toSourceString()
+                                + ": "
+                                + serviceImplementationType.toSourceString()
+                                + ".",
+                            serviceImplementationType,
+                            origin);
+                      }
+                    }
                     // Only keep one of each implementation type in the list.
                     return !serviceImplementations.contains(serviceImplementationType);
                   })
@@ -294,6 +329,12 @@
         int commentCharIndex = line.indexOf('#');
         return commentCharIndex > -1 ? line.substring(0, commentCharIndex) : line;
       }
+
+      private void warn(String message, DexType type, Origin origin) {
+        if (!options.getProguardConfiguration().getDontWarnPatterns().matches(type)) {
+          options.reporter.warning(new StringDiagnostic(message, origin));
+        }
+      }
     }
   }
 }
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 3850a41..282f852 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -521,6 +522,32 @@
     return !cfByteCodePassThrough.isEmpty();
   }
 
+  public void removePrunedClasses(
+      DirectMappedDexApplication prunedApp,
+      Set<DexType> removedClasses,
+      Collection<DexMethod> methodsToKeepForConfigurationDebugging) {
+    assert enableWholeProgramOptimizations();
+    assert appInfo().hasLiveness();
+    removePrunedClasses(
+        prunedApp, removedClasses, methodsToKeepForConfigurationDebugging, withLiveness());
+  }
+
+  private static void removePrunedClasses(
+      DirectMappedDexApplication prunedApp,
+      Set<DexType> removedClasses,
+      Collection<DexMethod> methodsToKeepForConfigurationDebugging,
+      AppView<AppInfoWithLiveness> appView) {
+    if (removedClasses.isEmpty() && !appView.options().configurationDebugging) {
+      assert appView.appInfo.app() == prunedApp;
+      return;
+    }
+    appView.setAppInfo(
+        appView
+            .appInfo()
+            .prunedCopyFrom(prunedApp, removedClasses, methodsToKeepForConfigurationDebugging));
+    appView.setAppServices(appView.appServices().prunedCopy(removedClasses));
+  }
+
   public void rewriteWithLens(NonIdentityGraphLens lens) {
     if (lens != null) {
       rewriteWithLens(lens, appInfo().app().asDirect(), withLiveness(), lens.getPrevious());
@@ -566,6 +593,7 @@
         GraphLens.getIdentityLens(),
         () -> {
           appView.setAppInfo(appView.appInfo().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/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 18ebdb4..cfccc2b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1778,23 +1778,22 @@
     return str.replace('.', '$');
   }
 
-  public DexString createFreshMemberString(String baseName, DexType holder, int index) {
-    StringBuilder sb =
-        new StringBuilder()
-            .append(baseName)
-            .append('$')
-            .append(escapeMemberString(holder.toSourceString()));
+  public String createMemberString(String baseName, DexType holder, int index) {
+    StringBuilder sb = new StringBuilder().append(baseName);
+    if (holder != null) {
+      sb.append('$').append(escapeMemberString(holder.toSourceString()));
+    }
 
     if (index > 0) {
       sb.append("$").append(index);
     }
 
-    return createString(sb.toString());
+    return sb.toString();
   }
 
   /**
    * Find a fresh method name that is not used by any other method. The method name takes the form
-   * "basename$holdername$index".
+   * "basename$holdername" or "basename$holdername$index".
    *
    * @param tryString callback to check if the method name is in use.
    */
@@ -1802,7 +1801,7 @@
       String baseName, DexType holder, Function<DexString, Optional<T>> tryString) {
     int index = 0;
     while (true) {
-      DexString name = createFreshMemberString(baseName, holder, index++);
+      DexString name = createString(createMemberString(baseName, holder, index++));
       Optional<T> result = tryString.apply(name);
       if (result.isPresent()) {
         return result.get();
@@ -1811,6 +1810,30 @@
   }
 
   /**
+   * Find a fresh method name that is not in the string pool. The name takes the form
+   * "basename$holdername" or "basename$holdername$index".
+   */
+  public DexString createGloballyFreshMemberString(String baseName, DexType holder) {
+    assert !sorted;
+    int index = 0;
+    while (true) {
+      String name = createMemberString(baseName, holder, index++);
+      DexString dexName = lookupString(name);
+      if (dexName == null) {
+        return createString(name);
+      }
+    }
+  }
+
+  /**
+   * Find a fresh method name that is not in the string pool. The name takes the form "basename" or
+   * "basename$index".
+   */
+  public DexString createGloballyFreshMemberString(String baseName) {
+    return createGloballyFreshMemberString(baseName, null);
+  }
+
+  /**
    * Tries to find a method name for insertion into the class {@code target} of the form
    * baseName$holder$n, where {@code baseName} and {@code holder} are supplied by the user, and
    * {@code n} is picked to be the first number so that {@code isFresh.apply(method)} returns {@code
@@ -1839,6 +1862,26 @@
     return method;
   }
 
+  public DexMethod createInstanceInitializerWithFreshProto(
+      DexMethod method, DexType extraType, Predicate<DexMethod> isFresh) {
+    assert method.isInstanceInitializer(this);
+    return createInstanceInitializerWithFreshProto(
+        method.proto,
+        extraType,
+        proto -> Optional.of(createMethod(method.holder, proto, method.name)).filter(isFresh));
+  }
+
+  private DexMethod createInstanceInitializerWithFreshProto(
+      DexProto proto, DexType extraType, Function<DexProto, Optional<DexMethod>> isFresh) {
+    while (true) {
+      Optional<DexMethod> object = isFresh.apply(proto);
+      if (object.isPresent()) {
+        return object.get();
+      }
+      proto = appendTypeToProto(proto, extraType);
+    }
+  }
+
   public DexString lookupString(int size, byte[] content) {
     return strings.get(new DexString(size, content));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index b3e11f5..128b353 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -247,4 +247,8 @@
   public DexMethod withHolder(DexType holder, DexItemFactory dexItemFactory) {
     return dexItemFactory.createMethod(holder, proto, name);
   }
+
+  public DexMethod withName(DexString name, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createMethod(holder, proto, name);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index a0cacd9..1eefc66 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -5,7 +5,9 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.naming.NamingLens;
+import com.google.common.collect.Iterables;
 import com.google.common.hash.Hasher;
+import java.util.Collections;
 import java.util.function.Consumer;
 
 public class DexProto extends IndexedDexItem implements PresortedComparable<DexProto> {
@@ -22,6 +24,18 @@
     this.parameters = parameters;
   }
 
+  public Iterable<DexType> getParameterBaseTypes(DexItemFactory dexItemFactory) {
+    return Iterables.transform(parameters, type -> type.toBaseType(dexItemFactory));
+  }
+
+  public Iterable<DexType> getBaseTypes(DexItemFactory dexItemFactory) {
+    return Iterables.transform(getTypes(), type -> type.toBaseType(dexItemFactory));
+  }
+
+  public Iterable<DexType> getTypes() {
+    return Iterables.concat(Collections.singleton(returnType), parameters);
+  }
+
   public void forEachType(Consumer<DexType> consumer) {
     consumer.accept(returnType);
     parameters.forEach(consumer);
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 ae25597..87080e8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -8,11 +8,13 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.ArrayUtils;
+import com.google.common.collect.Iterators;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
-public class DexTypeList extends DexItem {
+public class DexTypeList extends DexItem implements Iterable<DexType> {
 
   private static final DexTypeList theEmptyTypeList = new DexTypeList();
 
@@ -35,7 +37,8 @@
     return ArrayUtils.contains(values, type);
   }
 
-  public void forEach(Consumer<DexType> consumer) {
+  @Override
+  public void forEach(Consumer<? super DexType> consumer) {
     for (DexType value : values) {
       consumer.accept(value);
     }
@@ -121,4 +124,9 @@
     }
     throw new Unreachable();
   }
+
+  @Override
+  public Iterator<DexType> iterator() {
+    return Iterators.forArray(values);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index d95a723..a74f0c4 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -11,6 +11,7 @@
 import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
 import it.unimi.dsi.fastutil.objects.Object2ReferenceRBTreeMap;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.Map.Entry;
@@ -58,14 +59,6 @@
     }
   }
 
-  private void rehash() {
-    Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> newMap = createMap(methodMap.size());
-    for (DexEncodedMethod method : methodMap.values()) {
-      newMap.put(wrap(method.method), method);
-    }
-    methodMap = newMap;
-  }
-
   @Override
   boolean verify() {
     methodMap.forEach(
@@ -259,18 +252,14 @@
 
   @Override
   void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
-    boolean rehash = false;
-    for (Object2ReferenceMap.Entry<Wrapper<DexMethod>, DexEncodedMethod> entry :
-        methodMap.object2ReferenceEntrySet()) {
-      DexEncodedMethod newMethod = replacement.apply(entry.getValue());
-      if (newMethod != entry.getValue()) {
-        rehash = rehash || newMethod.method != entry.getKey().get();
-        entry.setValue(newMethod);
+    ArrayList<DexEncodedMethod> initialValues = new ArrayList<>(methodMap.values());
+    for (DexEncodedMethod method : initialValues) {
+      DexEncodedMethod newMethod = replacement.apply(method);
+      if (newMethod != method) {
+        removeMethod(method.method);
+        addMethod(newMethod);
       }
     }
-    if (rehash) {
-      rehash();
-    }
   }
 
   @Override
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 c80e363..73d1e68 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -44,6 +44,7 @@
   private final Collection<DexProgramClass> toMergeGroup;
   private final DexItemFactory dexItemFactory;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
+  private final HorizontallyMergedClasses.Builder mergedClassesBuilder;
   private final FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder;
 
   private final Reference2IntMap<DexType> classIdentifiers = new Reference2IntOpenHashMap<>();
@@ -54,6 +55,7 @@
   private ClassMerger(
       AppView<AppInfoWithLiveness> appView,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
+      HorizontallyMergedClasses.Builder mergedClassesBuilder,
       FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       DexProgramClass target,
       Collection<DexProgramClass> toMergeGroup,
@@ -62,6 +64,7 @@
       Collection<ConstructorMerger> constructorMergers) {
     this.appView = appView;
     this.lensBuilder = lensBuilder;
+    this.mergedClassesBuilder = mergedClassesBuilder;
     this.fieldAccessChangesBuilder = fieldAccessChangesBuilder;
     this.target = target;
     this.toMergeGroup = toMergeGroup;
@@ -156,9 +159,9 @@
   public void mergeGroup(SyntheticArgumentClass syntheticArgumentClass) {
     appendClassIdField();
 
+    mergedClassesBuilder.addMergeGroup(target, toMergeGroup);
     for (DexProgramClass clazz : toMergeGroup) {
       merge(clazz);
-      lensBuilder.mapType(clazz.type, target.type);
     }
 
     mergeConstructors(syntheticArgumentClass);
@@ -222,6 +225,7 @@
 
     public ClassMerger build(
         AppView<AppInfoWithLiveness> appView,
+        HorizontallyMergedClasses.Builder mergedClassesBuilder,
         HorizontalClassMergerGraphLens.Builder lensBuilder,
         FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder) {
       DexItemFactory dexItemFactory = appView.dexItemFactory();
@@ -244,6 +248,7 @@
       return new ClassMerger(
           appView,
           lensBuilder,
+          mergedClassesBuilder,
           fieldAccessChangesBuilder,
           target,
           toMergeGroup,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index 345905b..9baf6ad 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -140,7 +140,8 @@
         classFileVersion = Integer.max(classFileVersion, constructor.getClassFileVersion());
       }
       DexMethod movedConstructor = moveConstructor(constructor);
-      lensBuilder.recordOriginalSignature(constructor.method, movedConstructor);
+      lensBuilder.mapMethod(movedConstructor, movedConstructor);
+      lensBuilder.mapMethodInverse(constructor.method, movedConstructor);
       typeConstructorClassMap.put(
           classIdentifiers.getInt(constructor.getHolderType()), movedConstructor);
     }
@@ -152,14 +153,13 @@
       assert target.lookupMethod(newConstructorReference) == null;
     }
 
-    DexMethod originalConstructorReference =
-        appView.graphLens().getOriginalMethodSignature(constructors.iterator().next().method);
+    DexMethod representativeConstructorReference = constructors.iterator().next().method;
     ConstructorEntryPointSynthesizedCode synthesizedCode =
         new ConstructorEntryPointSynthesizedCode(
             typeConstructorClassMap,
             newConstructorReference,
             classIdField,
-            originalConstructorReference);
+            appView.graphLens().getOriginalMethodSignature(representativeConstructorReference));
     DexEncodedMethod newConstructor =
         new DexEncodedMethod(
             newConstructorReference,
@@ -177,10 +177,10 @@
       if (addExtraNull) {
         List<ExtraParameter> extraParameters = new LinkedList<>();
         extraParameters.add(new ExtraUnusedNullParameter());
-        lensBuilder.mapMergedConstructor(
+        lensBuilder.moveMergedConstructor(
             oldConstructor.method, newConstructorReference, extraParameters);
       } else {
-        lensBuilder.mapMethod(oldConstructor.method, newConstructorReference);
+        lensBuilder.moveMethod(oldConstructor.method, newConstructorReference);
       }
     } else {
       // Map each old constructor to the newly synthesized constructor in the graph lens.
@@ -193,12 +193,13 @@
           extraParameters.add(new ExtraUnusedNullParameter());
         }
 
-        lensBuilder.mapMergedConstructor(
+        lensBuilder.moveMergedConstructor(
             oldConstructor.method, newConstructorReference, extraParameters);
       }
     }
     // Map the first constructor to the newly synthesized constructor.
-    lensBuilder.recordExtraOriginalSignature(originalConstructorReference, newConstructorReference);
+    lensBuilder.recordExtraOriginalSignature(
+        representativeConstructorReference, newConstructorReference);
 
     target.addDirectMethod(newConstructor);
 
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 1886a48..d87bddc 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDex;
 import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
 import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
+import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
 import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ClassMergingEnqueuerExtension;
@@ -64,6 +65,7 @@
             new NotEntryPoint(appView.dexItemFactory()),
             new PreventMergeIntoMainDex(appView, mainDexTracingResult),
             new SameParentClass(),
+            new SameNestHost(),
             new PreventChangingVisibility(),
             new SameFeatureSplit(appView),
             new RespectPackageBoundaries(appView),
@@ -90,6 +92,8 @@
       return null;
     }
 
+    HorizontallyMergedClasses.Builder mergedClassesBuilder =
+        new HorizontallyMergedClasses.Builder();
     HorizontalClassMergerGraphLens.Builder lensBuilder =
         new HorizontalClassMergerGraphLens.Builder();
     FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder =
@@ -97,7 +101,8 @@
 
     // Set up a class merger for each group.
     Collection<ClassMerger> classMergers =
-        initializeClassMergers(lensBuilder, fieldAccessChangesBuilder, groups);
+        initializeClassMergers(
+            mergedClassesBuilder, lensBuilder, fieldAccessChangesBuilder, groups);
     Iterable<DexProgramClass> allMergeClasses =
         Iterables.concat(Iterables.transform(classMergers, ClassMerger::getClasses));
 
@@ -107,7 +112,10 @@
     applyClassMergers(classMergers, syntheticArgumentClass);
 
     // Generate the class lens.
-    return createLens(lensBuilder, fieldAccessChangesBuilder);
+    HorizontallyMergedClasses mergedClasses = mergedClassesBuilder.build();
+    appView.setHorizontallyMergedClasses(mergedClasses);
+    return createLens(
+        mergedClasses, lensBuilder, fieldAccessChangesBuilder, syntheticArgumentClass);
   }
 
   /**
@@ -115,6 +123,7 @@
    * be merged and how the merging should be performed.
    */
   private Collection<ClassMerger> initializeClassMergers(
+      HorizontallyMergedClasses.Builder mergedClassesBuilder,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       Collection<Collection<DexProgramClass>> groups) {
@@ -130,7 +139,7 @@
       ClassMerger merger =
           new ClassMerger.Builder(target)
               .addClassesToMerge(group)
-              .build(appView, lensBuilder, fieldAccessChangesBuilder);
+              .build(appView, mergedClassesBuilder, lensBuilder, fieldAccessChangesBuilder);
       classMergers.add(merger);
     }
 
@@ -150,11 +159,19 @@
    * containing all changes performed by horizontal class merging.
    */
   private HorizontalClassMergerGraphLens createLens(
+      HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
-      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder) {
+      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
+      SyntheticArgumentClass syntheticArgumentClass) {
 
     HorizontalClassMergerGraphLens lens =
-        new TreeFixer(appView, lensBuilder, fieldAccessChangesBuilder).fixupTypeReferences();
+        new TreeFixer(
+                appView,
+                mergedClasses,
+                lensBuilder,
+                fieldAccessChangesBuilder,
+                syntheticArgumentClass)
+            .fixupTypeReferences();
     return lens;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index 395409f..a10b066 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -7,18 +7,18 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
+import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 
 public class HorizontalClassMergerGraphLens extends NestedGraphLens {
   private final AppView<?> appView;
@@ -27,8 +27,8 @@
 
   private HorizontalClassMergerGraphLens(
       AppView<?> appView,
+      HorizontallyMergedClasses horizontallyMergedClasses,
       Map<DexMethod, List<ExtraParameter>> methodExtraParameters,
-      Map<DexType, DexType> typeMap,
       Map<DexField, DexField> fieldMap,
       Map<DexMethod, DexMethod> methodMap,
       BiMap<DexField, DexField> originalFieldSignatures,
@@ -36,7 +36,7 @@
       Map<DexMethod, DexMethod> originalConstructorSignatures,
       GraphLens previousLens) {
     super(
-        typeMap,
+        horizontallyMergedClasses.getForwardMap(),
         methodMap,
         fieldMap,
         originalFieldSignatures,
@@ -57,10 +57,6 @@
     return getPrevious().getOriginalMethodSignature(originalConstructor);
   }
 
-  public HorizontallyMergedClasses getHorizontallyMergedClasses() {
-    return new HorizontallyMergedClasses(this.typeMap);
-  }
-
   /**
    * If an overloaded constructor is requested, add the constructor id as a parameter to the
    * constructor. Otherwise return the lookup on the underlying graph lens.
@@ -81,102 +77,67 @@
   }
 
   public static class Builder {
-    private final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
     private final BiMap<DexField, DexField> fieldMap = HashBiMap.create();
-    private final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
-    private final Map<DexMethod, Set<DexMethod>> completeInverseMethodMap = new IdentityHashMap<>();
 
-    private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
-    private final Map<DexMethod, DexMethod> extraOriginalMethodSignatures = new IdentityHashMap<>();
+    private ManyToOneMap<DexMethod, DexMethod> methodMap = new ManyToOneMap<>();
 
     private final Map<DexMethod, List<ExtraParameter>> methodExtraParameters =
         new IdentityHashMap<>();
 
     Builder() {}
 
-    public HorizontalClassMergerGraphLens build(AppView<?> appView) {
-      assert !typeMap.isEmpty();
-
+    public HorizontalClassMergerGraphLens build(
+        AppView<?> appView, HorizontallyMergedClasses mergedClasses) {
+      ManyToOneInverseMap<DexMethod, DexMethod> inverseMethodMap =
+          methodMap.inverse(
+              group -> {
+                // Every group should have a representative. Fail in debug mode.
+                assert false;
+                return group.iterator().next();
+              });
       BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
+
       return new HorizontalClassMergerGraphLens(
           appView,
+          mergedClasses,
           methodExtraParameters,
-          typeMap,
           fieldMap,
-          methodMap,
+          methodMap.getForwardMap(),
           originalFieldSignatures,
-          originalMethodSignatures,
-          extraOriginalMethodSignatures,
+          inverseMethodMap.getBiMap(),
+          inverseMethodMap.getExtraMap(),
           appView.graphLens());
     }
 
-    public DexType lookupType(DexType type) {
-      return typeMap.getOrDefault(type, type);
-    }
-
-    public Builder mapType(DexType from, DexType to) {
-      typeMap.put(from, to);
-      return this;
-    }
-
-    /** Bidirectional mapping from one method to another. */
-    public Builder moveMethod(DexMethod from, DexMethod to) {
-      if (from == to) {
-        return this;
-      }
-
-      mapMethod(from, to);
-      recordOriginalSignature(from, to);
-      return this;
-    }
-
-    public Builder recordOriginalSignature(DexMethod from, DexMethod to) {
-      if (from == to) {
-        return this;
-      }
-
-      originalMethodSignatures.forcePut(to, originalMethodSignatures.getOrDefault(from, from));
-      return this;
+    public void remapMethods(BiMap<DexMethod, DexMethod> remapMethods) {
+      methodMap = methodMap.remap(remapMethods, Function.identity(), Function.identity());
     }
 
     /** Unidirectional mapping from one method to another. */
     public Builder recordExtraOriginalSignature(DexMethod from, DexMethod to) {
-      if (from == to) {
-        return this;
-      }
+      methodMap.setRepresentative(from, to);
 
-      extraOriginalMethodSignatures.put(to, extraOriginalMethodSignatures.getOrDefault(from, from));
       return this;
     }
 
     /** Unidirectional mapping from one method to another. */
     public Builder mapMethod(DexMethod from, DexMethod to) {
-      if (from == to) {
-        return this;
-      }
-
-      for (DexMethod existingFrom :
-          completeInverseMethodMap.getOrDefault(from, Collections.emptySet())) {
-        methodMap.put(existingFrom, to);
-
-        // We currently assume that a single method can only be remapped twice.
-        assert completeInverseMethodMap
-            .getOrDefault(existingFrom, Collections.emptySet())
-            .isEmpty();
-      }
-
       methodMap.put(from, to);
-      completeInverseMethodMap.computeIfAbsent(to, ignore -> new HashSet<>()).add(from);
 
       return this;
     }
 
-    public boolean hasExtraSignatureMappingFor(DexMethod method) {
-      return extraOriginalMethodSignatures.containsKey(method);
+    /** Unidirectional mapping from one method to another. */
+    public Builder mapMethodInverse(DexMethod from, DexMethod to) {
+      methodMap.putInverse(from, to);
+
+      return this;
     }
 
-    public boolean hasOriginalSignatureMappingFor(DexMethod method) {
-      return originalMethodSignatures.containsKey(method);
+    public Builder moveMethod(DexMethod from, DexMethod to) {
+      mapMethod(from, to);
+      mapMethodInverse(from, to);
+      return this;
     }
 
     /**
@@ -184,11 +145,24 @@
      * where many constructors are merged into a single constructor. The synthesized constructor
      * therefore does not have a unique reverse constructor.
      */
-    public Builder mapMergedConstructor(
+    public Builder moveMergedConstructor(
         DexMethod from, DexMethod to, List<ExtraParameter> extraParameters) {
-      mapMethod(from, to);
+      moveMethod(from, to);
       methodExtraParameters.put(from, extraParameters);
       return this;
     }
+
+    public Builder addExtraParameters(DexMethod to, List<ExtraParameter> extraParameters) {
+      Set<DexMethod> mapsFrom = methodMap.lookupReverse(to);
+      if (mapsFrom == null) {
+        mapsFrom = Collections.singleton(to);
+      }
+      mapsFrom.forEach(
+          originalFrom ->
+              methodExtraParameters
+                  .computeIfAbsent(originalFrom, ignore -> new ArrayList<>(extraParameters.size()))
+                  .addAll(extraParameters));
+      return this;
+    }
   }
 }
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 00632e5..b912cf7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -5,21 +5,49 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.classmerging.MergedClasses;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
+import java.util.Collection;
 import java.util.Map;
 
 public class HorizontallyMergedClasses implements MergedClasses {
-  private final Map<DexType, DexType> horizontallyMergedClasses;
+  private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses;
 
-  public HorizontallyMergedClasses(Map<DexType, DexType> horizontallyMergedClasses) {
-    this.horizontallyMergedClasses = horizontallyMergedClasses;
+  public HorizontallyMergedClasses(BidirectionalManyToOneMap<DexType, DexType> mergedClasses) {
+    this.mergedClasses = mergedClasses;
+  }
+
+  public DexType getMergeTargetOrDefault(DexType type) {
+    return mergedClasses.getOrDefault(type, type);
+  }
+
+  public boolean hasBeenMergedIntoDifferentType(DexType type) {
+    return mergedClasses.hasKey(type);
+  }
+
+  @Override
+  public boolean hasBeenMerged(DexType type) {
+    return hasBeenMergedIntoDifferentType(type);
+  }
+
+  public boolean isMergeTarget(DexType type) {
+    return mergedClasses.hasValue(type);
+  }
+
+  public boolean hasBeenMergedOrIsMergeTarget(DexType type) {
+    return hasBeenMerged(type) || isMergeTarget(type);
+  }
+
+  Map<DexType, DexType> getForwardMap() {
+    return mergedClasses.getForwardMap();
   }
 
   @Override
   public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
-    for (DexType source : horizontallyMergedClasses.keySet()) {
+    for (DexType source : mergedClasses.keySet()) {
       assert appView.appInfo().wasPruned(source)
           : "Expected horizontally merged lambda class `"
               + source.toSourceString()
@@ -28,8 +56,18 @@
     return true;
   }
 
-  @Override
-  public boolean hasBeenMerged(DexType type) {
-    return horizontallyMergedClasses.containsKey(type);
+  public static class Builder {
+    private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses =
+        new BidirectionalManyToOneMap<>();
+
+    public HorizontallyMergedClasses build() {
+      return new HorizontallyMergedClasses(mergedClasses);
+    }
+
+    public void addMergeGroup(DexProgramClass target, Collection<DexProgramClass> toMergeGroup) {
+      for (DexProgramClass clazz : toMergeGroup) {
+        mergedClasses.put(clazz.type, target.type);
+      }
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneInverseMap.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneInverseMap.java
new file mode 100644
index 0000000..8da9b0f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneInverseMap.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.google.common.collect.BiMap;
+import java.util.Map;
+
+/** The inverse of a {@link ManyToOneMap} used for generating graph lens maps. */
+public class ManyToOneInverseMap<K, V> {
+  private final BiMap<V, K> biMap;
+  private final Map<V, K> extraMap;
+
+  ManyToOneInverseMap(BiMap<V, K> biMap, Map<V, K> extraMap) {
+    this.biMap = biMap;
+    this.extraMap = extraMap;
+  }
+
+  public BiMap<V, K> getBiMap() {
+    return biMap;
+  }
+
+  public Map<V, K> getExtraMap() {
+    return extraMap;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneMap.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneMap.java
new file mode 100644
index 0000000..675d24c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneMap.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * This mapping class is used to track method mappings for horizontal class merging. Essentially it
+ * is a bidirectional many to one map, but with support for having unidirectional mappings and with
+ * support for remapping the values to new values using {@link ManyToOneMap#remap(BiMap, Function,
+ * Function)}. It also supports generating an inverse mapping {@link ManyToOneInverseMap} that can
+ * be used by the graph lens using {@link ManyToOneMap#inverse(Function)}. The inverse map is a
+ * bidirectional one to one map with additional non-bidirectional representative entries.
+ */
+public class ManyToOneMap<K, V> {
+  private final Map<K, V> forwardMap = new IdentityHashMap<>();
+  private final Map<V, Set<K>> inverseMap = new IdentityHashMap<>();
+  private final Map<V, K> representativeMap = new IdentityHashMap<>();
+
+  public Map<K, V> getForwardMap() {
+    return forwardMap;
+  }
+
+  public Set<K> lookupReverse(V to) {
+    return inverseMap.get(to);
+  }
+
+  public V put(K from, V to) {
+    return forwardMap.put(from, to);
+  }
+
+  public void putInverse(K from, V to) {
+    inverseMap.computeIfAbsent(to, ignore -> new HashSet<>()).add(from);
+  }
+
+  public K setRepresentative(K from, V to) {
+    putInverse(from, to);
+    return representativeMap.put(to, from);
+  }
+
+  public ManyToOneInverseMap<K, V> inverse(Function<Set<K>, K> pickRepresentative) {
+    BiMap<V, K> biMap = HashBiMap.create();
+    Map<V, K> extraMap = new HashMap<>();
+    for (Entry<V, Set<K>> entry : inverseMap.entrySet()) {
+      K representative = representativeMap.get(entry.getKey());
+      if (entry.getValue().size() == 1) {
+        K singleton = entry.getValue().iterator().next();
+        assert representative == null || singleton == representative;
+        if (representative == null) {
+          biMap.put(entry.getKey(), singleton);
+        } else {
+          extraMap.put(entry.getKey(), singleton);
+        }
+      } else {
+        if (representative == null) {
+          representative = pickRepresentative.apply(entry.getValue());
+        } else {
+          assert representative == entry.getKey() || entry.getValue().contains(representative);
+        }
+        extraMap.put(entry.getKey(), representative);
+      }
+    }
+
+    return new ManyToOneInverseMap<>(biMap, extraMap);
+  }
+
+  public <NewV> ManyToOneMap<K, NewV> remap(
+      BiMap<V, NewV> biMap, Function<V, NewV> notInBiMap, Function<V, K> notInForwardMap) {
+    ManyToOneMap<K, NewV> newMap = new ManyToOneMap<>();
+
+    // All entries that should be remapped and are already in the forward and/or inverse mappings
+    // should only be remapped in the directions they are already mapped in.
+    BiMap<V, NewV> biMapCopy = HashBiMap.create(biMap);
+    for (Entry<V, Set<K>> entry : inverseMap.entrySet()) {
+      NewV to = biMapCopy.remove(entry.getKey());
+      if (to == null) {
+        to = biMap.getOrDefault(entry.getKey(), notInBiMap.apply(entry.getKey()));
+      }
+      newMap.inverseMap.put(to, entry.getValue());
+    }
+    for (Entry<K, V> entry : forwardMap.entrySet()) {
+      NewV newTo = biMapCopy.remove(entry.getValue());
+      if (newTo == null) {
+        newTo = biMap.getOrDefault(entry.getValue(), notInBiMap.apply(entry.getValue()));
+      }
+      newMap.forwardMap.put(entry.getKey(), newTo);
+    }
+
+    // All new entries should be mapped in both directions.
+    for (Entry<V, NewV> entry : biMapCopy.entrySet()) {
+      newMap.forwardMap.put(notInForwardMap.apply(entry.getKey()), entry.getValue());
+      newMap
+          .inverseMap
+          .computeIfAbsent(entry.getValue(), ignore -> new HashSet<>())
+          .add(notInForwardMap.apply(entry.getKey()));
+    }
+
+    // Representatives are always in the inverse mapping, so they should always be remapped as new
+    // representatives.
+    for (Entry<V, K> entry : representativeMap.entrySet()) {
+      NewV newTo = biMap.get(entry.getKey());
+      if (newTo == null) {
+        newTo = notInBiMap.apply(entry.getKey());
+      }
+      newMap.representativeMap.put(newTo, entry.getValue());
+    }
+
+    return newMap;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
index 3407306..0a7e988 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
@@ -11,7 +11,6 @@
 import java.util.Map;
 
 public abstract class MultiClassSameReferencePolicy<T> extends MultiClassPolicy {
-
   @Override
   public final Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) {
     Map<T, Collection<DexProgramClass>> groups = new IdentityHashMap<>();
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 8d6fc72..f6b86a3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
@@ -9,6 +9,9 @@
  * {@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 boolean shouldSkipPolicy() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
index f0ac605..6192da8 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
@@ -26,7 +26,9 @@
     Iterator<Collection<DexProgramClass>> i = groups.iterator();
     while (i.hasNext()) {
       Collection<DexProgramClass> group = i.next();
+      int previousNumberOfClasses = group.size();
       group.removeIf(clazz -> !policy.canMerge(clazz));
+      policy.numberOfRemovedClasses += previousNumberOfClasses - group.size();
       if (group.size() < 2) {
         i.remove();
       }
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 a35db17..b57adbf 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -9,17 +9,31 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.shaking.AnnotationFixer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.OptionalBool;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * The tree fixer traverses all program classes and finds and fixes references to old classes which
@@ -28,49 +42,221 @@
  */
 class TreeFixer {
   private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
+  private final HorizontallyMergedClasses mergedClasses;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
   private final FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder;
   private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory dexItemFactory;
+  private final BiMap<DexMethod, DexMethod> movedMethods = HashBiMap.create();
+  private final SyntheticArgumentClass syntheticArgumentClass;
+  private final BiMap<Wrapper<DexMethod>, Wrapper<DexMethod>> reservedInterfaceSignatures =
+      HashBiMap.create();
+
+  // Store which methods have been renamed in parent classes.
+  private final Map<DexType, Map<Wrapper<DexMethod>, DexString>> renamedVirtualMethods =
+      new IdentityHashMap<>();
 
   public TreeFixer(
       AppView<AppInfoWithLiveness> appView,
+      HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
-      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder) {
+      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
+      SyntheticArgumentClass syntheticArgumentClass) {
+    this.appView = appView;
+    this.mergedClasses = mergedClasses;
     this.lensBuilder = lensBuilder;
     this.fieldAccessChangesBuilder = fieldAccessChangesBuilder;
-    this.appView = appView;
+    this.syntheticArgumentClass = syntheticArgumentClass;
+    this.dexItemFactory = appView.dexItemFactory();
   }
 
-  HorizontalClassMergerGraphLens fixupTypeReferences() {
-    // Globally substitute merged class types in protos and holders.
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      clazz.superType = lensBuilder.lookupType(clazz.superType);
-      clazz.getMethodCollection().replaceMethods(this::fixupMethod);
-      fixupFields(clazz.staticFields(), clazz::setStaticField);
-      fixupFields(clazz.instanceFields(), clazz::setInstanceField);
-    }
-    HorizontalClassMergerGraphLens lens = lensBuilder.build(appView);
-    fieldAccessChangesBuilder.build(this::fixupMethod).modify(appView);
+  /**
+   * Lets assume the following initial classes, where the class B should be merged into A: <code>
+   *   class A {
+   *     public A(A a) { ... }
+   *     public A(A a, int v) { ... }
+   *     public A(B b) { ... }
+   *     public A(B b, int v) { ... }
+   *   }
+   *
+   *   class B {
+   *     public B(A a) { ... }
+   *     public B(B b) { ... }
+   *   }
+   * </code>
+   *
+   * <p>The {@link ClassMerger} merges the constructors {@code A.<init>(B)} and {@code B.<init>(B)}
+   * into the constructor {@code A.<init>(B, int)} to prevent any collisions when merging the
+   * constructor into A. The extra integer argument determines which class' constructor is called.
+   * The SynthArg is used to prevent a collision with the existing {@code A.<init>(B, int)}
+   * constructor. All constructors {@code A.<init>(A, ...)} generate a constructor {@code
+   * A.<init>(A, int, SynthClass)} but are otherwise ignored. During ClassMerging the constructor
+   * produces the following mappings in the graph lens builder:
+   *
+   * <ul>
+   *   <li>{@code B.<init>(B) <--> A.<init>(B, int, SynthArg)}
+   *   <li>{@code A.<init>(B) <--> A.<init>(B, int, SynthArg)} (This mapping is representative)
+   *   <li>{@code A.constructor$B(B) ---> A.constructor$B(B)}
+   *   <li>{@code B.<init>(B) <--- A.constructor$B(B)}
+   * </ul>
+   *
+   * <p>Note: The identity mapping is needed so that the method is remapped in the forward direction
+   * if there are changes in the tree fixer. Otherwise, methods are only remapped in directions they
+   * are already mapped in.
+   *
+   * <p>During the fixup, all type references to B are changed into A. This causes a collision
+   * between {@code A.<init>(A, int, SynthClass)} and {@code A.<init>(B, int, SynthClass)}. This
+   * collision should be fixed by adding an extra argument to {@code A.<init>(B, int, SynthClass)}.
+   * The TreeFixer generates the following mapping of renamed methods:
+   *
+   * <ul>
+   *   <li>{@code A.<init>(B, int, SynthArg) <--> A.<init>(A, int, SynthArg, ExtraArg)}
+   *   <li>{@code A.constructor$B(B) <--> A.constructor$B(A)}
+   * </ul>
+   *
+   * <p>This rewrites the previous method mappings to:
+   *
+   * <ul>
+   *   <li>{@code B.<init>(B) <--- A.constructor$B(A)}
+   *   <li>{@code A.constructor$B(B) ---> A.constructor$B(A)}
+   *   <li>{@code B.<init>(B) <--> A.<init>(A, int, SynthArg, ExtraArg)}
+   *   <li>{@code A.<init>(B) <--> A.<init>(A, int, SynthArg, ExtraArg)} (including represents)
+   * </ul>
+   */
+  public HorizontalClassMergerGraphLens fixupTypeReferences() {
+    Iterable<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
+    Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
+
+    forEachClassTypeTraverseHierarchy(
+        Iterables.filter(classes, clazz -> !clazz.isInterface()), this::fixupProgramClass);
+
+    lensBuilder.remapMethods(movedMethods);
+
+    HorizontalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses);
+    fieldAccessChangesBuilder.build(this::fixupMethodReference).modify(appView);
     new AnnotationFixer(lens).run(appView.appInfo().classes());
     return lens;
   }
 
-  private DexEncodedMethod fixupMethod(DexEncodedMethod method) {
-    DexMethod methodReference = method.method;
-    DexMethod newMethodReference = fixupMethod(methodReference);
-    if (newMethodReference == methodReference) {
+  private void fixupProgramClass(DexProgramClass clazz) {
+    assert !clazz.isInterface();
+
+    // TODO(b/169395592): ensure merged classes have been removed using:
+    //   assert !mergedClasses.hasBeenMergedIntoDifferentType(clazz.type);
+
+    Map<Wrapper<DexMethod>, DexString> renamedClassVirtualMethods =
+        new HashMap<>(renamedVirtualMethods.getOrDefault(clazz.superType, new HashMap<>()));
+
+    Set<DexMethod> newDirectMethodReferences = new LinkedHashSet<>();
+    Set<DexMethod> newVirtualMethodReferences = new LinkedHashSet<>();
+
+    clazz
+        .getMethodCollection()
+        .replaceVirtualMethods(
+            method ->
+                fixupVirtualMethod(renamedClassVirtualMethods, newVirtualMethodReferences, method));
+    clazz
+        .getMethodCollection()
+        .replaceDirectMethods(method -> fixupDirectMethod(newDirectMethodReferences, method));
+
+    if (!renamedClassVirtualMethods.isEmpty()) {
+      renamedVirtualMethods.put(clazz.type, renamedClassVirtualMethods);
+    }
+    fixupFields(clazz.staticFields(), clazz::setStaticField);
+    fixupFields(clazz.instanceFields(), clazz::setInstanceField);
+  }
+
+  private void traverseUp(
+      DexProgramClass clazz, Set<DexProgramClass> seenClasses, Consumer<DexProgramClass> fn) {
+    if (clazz == null || !seenClasses.add(clazz)) {
+      return;
+    }
+
+    clazz.superType = mergedClasses.getMergeTargetOrDefault(clazz.superType);
+    if (clazz.superType != null) {
+      DexProgramClass superClass = appView.programDefinitionFor(clazz.superType, clazz);
+      traverseUp(superClass, seenClasses, fn);
+    }
+
+    fn.accept(clazz);
+  }
+
+  private void forEachClassTypeTraverseHierarchy(
+      Iterable<DexProgramClass> classes, Consumer<DexProgramClass> fn) {
+    Set<DexProgramClass> seenClasses = Sets.newIdentityHashSet();
+    for (DexProgramClass clazz : classes) {
+      traverseUp(clazz, seenClasses, fn);
+    }
+  }
+
+  private DexEncodedMethod fixupVirtualInterfaceMethod(DexEncodedMethod method) {
+    DexMethod originalMethodReference = method.getReference();
+
+    // Don't process this method if it does not refer to a merge class type.
+    boolean referencesMergeClass =
+        Iterables.any(
+            originalMethodReference.proto.getBaseTypes(dexItemFactory),
+            mergedClasses::hasBeenMergedOrIsMergeTarget);
+    if (!referencesMergeClass) {
       return method;
     }
 
-    // If the method is a synthesized method, then don't record the original signature.
-    if ((method.getCode() instanceof ConstructorEntryPointSynthesizedCode)) {
-      assert lensBuilder.hasExtraSignatureMappingFor(methodReference);
-      lensBuilder.recordExtraOriginalSignature(methodReference, newMethodReference);
-      lensBuilder.mapMethod(methodReference, newMethodReference);
-    } else {
-      lensBuilder.moveMethod(methodReference, newMethodReference);
+    MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
+    Wrapper<DexMethod> originalMethodSignature = equivalence.wrap(originalMethodReference);
+    Wrapper<DexMethod> newMethodSignature =
+        reservedInterfaceSignatures.get(originalMethodSignature);
+
+    if (newMethodSignature == null) {
+      newMethodSignature = equivalence.wrap(fixupMethodReference(originalMethodReference));
+
+      // If the signature is already reserved by another interface, find a fresh one.
+      if (reservedInterfaceSignatures.containsValue(newMethodSignature)) {
+        DexString name =
+            dexItemFactory.createGloballyFreshMemberString(
+                originalMethodReference.getName().toSourceString());
+        newMethodSignature =
+            equivalence.wrap(
+                dexItemFactory.createMethod(
+                    newMethodSignature.get().holder, newMethodSignature.get().proto, name));
+      }
+
+      assert !reservedInterfaceSignatures.containsValue(newMethodSignature);
+      reservedInterfaceSignatures.put(originalMethodSignature, newMethodSignature);
     }
 
+    DexMethod newMethodReference =
+        newMethodSignature
+            .get()
+            .withHolder(originalMethodReference.getHolderType(), dexItemFactory);
+    movedMethods.put(originalMethodReference, newMethodReference);
+
+    return method.toTypeSubstitutedMethod(newMethodReference);
+  }
+
+  private void fixupInterfaceClass(DexProgramClass iface) {
+    Set<DexMethod> newDirectMethods = new LinkedHashSet<>();
+
+    assert iface.superType == dexItemFactory.objectType;
+    iface.superType = mergedClasses.getMergeTargetOrDefault(iface.superType);
+
+    iface
+        .getMethodCollection()
+        .replaceDirectMethods(method -> fixupDirectMethod(newDirectMethods, method));
+    iface.getMethodCollection().replaceVirtualMethods(this::fixupVirtualInterfaceMethod);
+    fixupFields(iface.staticFields(), iface::setStaticField);
+    fixupFields(iface.instanceFields(), iface::setInstanceField);
+  }
+
+  private DexEncodedMethod fixupProgramMethod(
+      DexMethod newMethodReference, DexEncodedMethod method) {
+    DexMethod originalMethodReference = method.getReference();
+
+    if (newMethodReference == originalMethodReference) {
+      return method;
+    }
+
+    movedMethods.put(originalMethodReference, newMethodReference);
+
     DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(newMethodReference);
     if (newMethod.isNonPrivateVirtualMethod()) {
       // Since we changed the return type or one of the parameters, this method cannot be a
@@ -78,9 +264,122 @@
       assert !method.isLibraryMethodOverride().isTrue();
       newMethod.setLibraryMethodOverride(OptionalBool.FALSE);
     }
+
     return newMethod;
   }
 
+  private DexEncodedMethod fixupDirectMethod(Set<DexMethod> newMethods, DexEncodedMethod method) {
+    DexMethod originalMethodReference = method.getReference();
+
+    // Fix all type references in the method prototype.
+    DexMethod newMethodReference = fixupMethodReference(originalMethodReference);
+
+    if (newMethods.contains(newMethodReference)) {
+      // If the method collides with a direct method on the same class then rename it to a globally
+      // fresh name and record the signature.
+
+      if (method.isInstanceInitializer()) {
+        // If the method is an instance initializer, then add extra nulls.
+        newMethodReference =
+            dexItemFactory.createInstanceInitializerWithFreshProto(
+                newMethodReference,
+                syntheticArgumentClass.getArgumentClass(),
+                tryMethod -> !newMethods.contains(tryMethod));
+        int extraNulls = newMethodReference.getArity() - originalMethodReference.getArity();
+        lensBuilder.addExtraParameters(
+            originalMethodReference,
+            Collections.nCopies(extraNulls, new ExtraUnusedNullParameter()));
+      } else {
+        DexString newMethodName =
+            dexItemFactory.createGloballyFreshMemberString(
+                originalMethodReference.getName().toSourceString(), null);
+        newMethodReference =
+            dexItemFactory.createMethod(
+                newMethodReference.holder, newMethodReference.proto, newMethodName);
+      }
+    }
+
+    boolean changed = newMethods.add(newMethodReference);
+    assert changed;
+
+    return fixupProgramMethod(newMethodReference, method);
+  }
+
+  private DexString lookupReservedVirtualName(
+      DexMethod originalMethodReference,
+      Map<Wrapper<DexMethod>, DexString> renamedClassVirtualMethods) {
+    Wrapper<DexMethod> originalSignature =
+        MethodSignatureEquivalence.get().wrap(originalMethodReference);
+
+    // Determine if the original method has been rewritten by a parent class
+    DexString renamedVirtualName =
+        renamedClassVirtualMethods != null
+            ? renamedClassVirtualMethods.get(originalSignature)
+            : null;
+
+    if (renamedVirtualName == null) {
+      // Determine if there is a signature mapping.
+      Wrapper<DexMethod> mappedInterfaceSignature =
+          reservedInterfaceSignatures.get(originalSignature);
+      if (mappedInterfaceSignature != null) {
+        renamedVirtualName = mappedInterfaceSignature.get().name;
+      }
+    } else {
+      assert !reservedInterfaceSignatures.containsKey(originalSignature);
+    }
+
+    return renamedVirtualName;
+  }
+
+  private DexEncodedMethod fixupVirtualMethod(
+      Map<Wrapper<DexMethod>, DexString> renamedClassVirtualMethods,
+      Set<DexMethod> newMethods,
+      DexEncodedMethod method) {
+    DexMethod originalMethodReference = method.getReference();
+    Wrapper<DexMethod> originalSignature =
+        MethodSignatureEquivalence.get().wrap(originalMethodReference);
+
+    DexString renamedVirtualName =
+        lookupReservedVirtualName(originalMethodReference, renamedClassVirtualMethods);
+
+    // Fix all type references in the method prototype.
+    DexMethod newMethodReference = fixupMethodReference(originalMethodReference);
+    Wrapper<DexMethod> newSignature = MethodSignatureEquivalence.get().wrap(newMethodReference);
+
+    if (renamedVirtualName != null) {
+      // If the method was renamed in a parent, rename it in the child.
+      newMethodReference = newMethodReference.withName(renamedVirtualName, dexItemFactory);
+
+      assert !newMethods.contains(newMethodReference);
+    } else if (reservedInterfaceSignatures.containsValue(newSignature)
+        || newMethods.contains(newMethodReference)) {
+      // If the method potentially collides with an interface method or with another virtual method
+      // rename it to a globally fresh name and record the name.
+
+      DexString newMethodName =
+          dexItemFactory.createGloballyFreshMemberString(
+              originalMethodReference.getName().toSourceString(), null);
+      newMethodReference = newMethodReference.withName(newMethodName, dexItemFactory);
+
+      // Record signature renaming so that subclasses perform the identical rename.
+      renamedClassVirtualMethods.put(originalSignature, newMethodReference.getName());
+    } else {
+      // There was no reserved name and the new signature is available.
+
+      if (Iterables.any(
+          newMethodReference.proto.getParameterBaseTypes(dexItemFactory),
+          mergedClasses::isMergeTarget)) {
+        // If any of the parameter types have been merged, record the signature mapping.
+        renamedClassVirtualMethods.put(originalSignature, newMethodReference.getName());
+      }
+    }
+
+    boolean changed = newMethods.add(newMethodReference);
+    assert changed;
+
+    return fixupProgramMethod(newMethodReference, method);
+  }
+
   private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
     if (fields == null) {
       return;
@@ -88,9 +387,7 @@
     for (int i = 0; i < fields.size(); i++) {
       DexEncodedField encodedField = fields.get(i);
       DexField field = encodedField.field;
-      DexType newType = fixupType(field.type);
-      DexType newHolder = fixupType(field.holder);
-      DexField newField = appView.dexItemFactory().createField(newHolder, newType, field.name);
+      DexField newField = fixupFieldReference(field);
       if (newField != encodedField.field) {
         // TODO(b/165498187): track mapped fields
         /* lensBuilder.map(field, newField); */
@@ -99,7 +396,13 @@
     }
   }
 
-  private DexMethod fixupMethod(DexMethod method) {
+  public DexField fixupFieldReference(DexField field) {
+    DexType newType = fixupType(field.type);
+    DexType newHolder = fixupType(field.holder);
+    return appView.dexItemFactory().createField(newHolder, newType, field.name);
+  }
+
+  private DexMethod fixupMethodReference(DexMethod method) {
     return appView
         .dexItemFactory()
         .createMethod(fixupType(method.holder), fixupProto(method.proto), method.name);
@@ -126,11 +429,7 @@
       return type.replaceBaseType(fixed, appView.dexItemFactory());
     }
     if (type.isClassType()) {
-      while (true) {
-        DexType mapped = lensBuilder.lookupType(type);
-        if (mapped == type) break;
-        type = mapped;
-      }
+      type = mergedClasses.getMergeTargetOrDefault(type);
     }
     return type;
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index c8f2f6d..2bf2b97 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -145,7 +145,8 @@
             Integer.max(classFileVersion, method.getDefinition().getClassFileVersion());
       }
       DexMethod newMethod = moveMethod(method);
-      lensBuilder.recordOriginalSignature(method.getReference(), newMethod);
+      lensBuilder.mapMethod(newMethod, newMethod);
+      lensBuilder.mapMethodInverse(method.getReference(), newMethod);
       classIdToMethodMap.put(classIdentifiers.getInt(method.getHolderType()), newMethod);
     }
 
@@ -175,7 +176,7 @@
 
     // Map each old method to the newly synthesized method in the graph lens.
     for (ProgramMethod oldMethod : methods) {
-      lensBuilder.mapMethod(oldMethod.getReference(), newMethodReference);
+      lensBuilder.moveMethod(oldMethod.getReference(), newMethodReference);
     }
     lensBuilder.recordExtraOriginalSignature(originalMethodReference, newMethodReference);
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
new file mode 100644
index 0000000..6619878
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
@@ -0,0 +1,16 @@
+// 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.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+
+public class SameNestHost extends MultiClassSameReferencePolicy<DexType> {
+  @Override
+  public DexType getMergeKey(DexProgramClass clazz) {
+    return clazz.getNestHost();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java
index 5b274d1..12c8e31 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java
@@ -6,23 +6,12 @@
 
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
-import java.util.Collection;
-import java.util.IdentityHashMap;
-import java.util.LinkedList;
-import java.util.Map;
+import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
 
-public class SameParentClass extends MultiClassPolicy {
+public class SameParentClass extends MultiClassSameReferencePolicy<DexType> {
 
   @Override
-  public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) {
-    Map<DexType, Collection<DexProgramClass>> groups = new IdentityHashMap<>();
-    for (DexProgramClass clazz : group) {
-      groups
-          .computeIfAbsent(clazz.superType, ignore -> new LinkedList<DexProgramClass>())
-          .add(clazz);
-    }
-    removeTrivialGroups(groups.values());
-    return groups.values();
+  public DexType getMergeKey(DexProgramClass clazz) {
+    return clazz.superType;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 0df3c80..3bc0ebe 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -311,7 +311,6 @@
             DexEncodedField field =
                 appView.appInfo().resolveField(info.getField()).getResolvedField();
             if (field == null) {
-              assert false;
               return;
             }
             if (!info.hasReflectiveAccess() && !info.isWrittenFromMethodHandle()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
new file mode 100644
index 0000000..055882b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
@@ -0,0 +1,148 @@
+// 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.ir.analysis.proto;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+/**
+ * EnumLite proto enums include the following code: <code>
+ * private static final EnumLiteMap<ProtoEnum> internalValueMap =
+ *   new EnumLiteMap<ProtoEnum>() {
+ *     public ProtoEnum findValueByNumber(int number) {
+ *       return ProtoEnum.forNumber(number);
+ *     }
+ *   };
+ * </code>
+ *
+ * <p>If the field internalValueMap on the EnumLite is effectively unused (never read), the
+ * anonymous subclass of EnumLiteMap is effectively dead. R8 figures that out however too late,
+ * during the second round of tree shaking, and the bridge findValueByNumber in the anonymous
+ * subclass of EnumLiteMap prevents the EnumLite to be unboxed.
+ *
+ * <p>This class prematurely clears the virtual methods of the anonymous subclasses of EnumLiteMap,
+ * when it can prove that the internalValueMap field of the corresponding EnumLite is never read.
+ */
+public class EnumLiteProtoShrinker {
+
+  private AppView<AppInfoWithLiveness> appView;
+  private ProtoReferences references;
+  private Set<DexType> deadEnumLiteMaps = Sets.newIdentityHashSet();
+
+  public EnumLiteProtoShrinker(AppView<AppInfoWithLiveness> appView, ProtoReferences references) {
+    this.appView = appView;
+    this.references = references;
+  }
+
+  private DexField createInternalValueMapField(DexType holder) {
+    return appView
+        .dexItemFactory()
+        .createField(holder, references.enumLiteMapType, references.internalValueMapFieldName);
+  }
+
+  public void clearDeadEnumLiteMaps() {
+    if (!appView.options().protoShrinking().enableEnumLiteProtoShrinking) {
+      return;
+    }
+    // The optimization only enables further enums to be unboxed, no point to run it if enum
+    // unboxing is disabled.
+    if (!appView.options().enableEnumUnboxing) {
+      return;
+    }
+    // The optimization relies on shrinking and member value propagation to actually clear
+    // the anonymous subclasses of EnumLiteMap.
+    if (!appView.options().isShrinking() || !appView.options().enableValuePropagation) {
+      return;
+    }
+    internalClearDeadEnumLiteMaps();
+  }
+
+  private void internalClearDeadEnumLiteMaps() {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (clazz.interfaces.contains(references.enumLiteMapType)) {
+        DexProgramClass enumLite = computeCorrespondingEnumLite(clazz);
+        if (enumLite != null) {
+          DexEncodedField field = enumLite.lookupField(createInternalValueMapField(enumLite.type));
+          if (field != null) {
+            if (appView.appInfo().isStaticFieldWrittenOnlyInEnclosingStaticInitializer(field)
+                && !appView.appInfo().isFieldRead(field)) {
+              deadEnumLiteMaps.add(clazz.type);
+              // Clears the EnumLiteMap methods to avoid them being IR processed.
+              clazz.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Each EnumLiteMap subclass has only two virtual methods findValueByNumber:
+   *
+   * <ul>
+   *   <li>EnumLite findValueByNumber(int)
+   *   <li>ConcreteEnumLiteSubType findValueByNumber(int)
+   * </ul>
+   *
+   * <p>The method with the EnumLite return type is the bridge, we extract the concrete EnumLite
+   * subtype from the other method return type.
+   *
+   * <p>We bail out if other virtual methods than the two expected ones are found and return null.
+   */
+  private DexProgramClass computeCorrespondingEnumLite(DexProgramClass enumLiteMap) {
+    if (enumLiteMap.getMethodCollection().numberOfVirtualMethods() != 2) {
+      return null;
+    }
+    DexType enumLiteCandidate = null;
+    for (DexEncodedMethod virtualMethod : enumLiteMap.virtualMethods()) {
+      if (!matchesFindValueByNumberMethod(virtualMethod.method)) {
+        return null;
+      }
+      if (virtualMethod.returnType() == references.enumLiteType) {
+        continue;
+      }
+      if (enumLiteCandidate != null) {
+        return null;
+      }
+      enumLiteCandidate = virtualMethod.returnType();
+    }
+    if (enumLiteCandidate == null) {
+      return null;
+    }
+    DexProgramClass enumLite = appView.programDefinitionFor(enumLiteCandidate, enumLiteMap);
+    if (enumLite != null
+        && enumLite.isEnum()
+        && enumLite.interfaces.contains(references.enumLiteType)) {
+      return enumLite;
+    }
+    return null;
+  }
+
+  private boolean matchesFindValueByNumberMethod(DexMethod method) {
+    return method.name == references.findValueByNumberName
+        && method.getArity() == 1
+        && method.getParameters().values[0] == appView.dexItemFactory().intType;
+  }
+
+  public void verifyDeadEnumLiteMapsAreDead() {
+    for (DexType deadEnumLiteMap : deadEnumLiteMaps) {
+      if (appView.appInfo().definitionForWithoutExistenceAssert(deadEnumLiteMap) != null) {
+        throw new CompilationError(
+            "EnumLite Proto Shrinker failure: Type "
+                + deadEnumLiteMap
+                + " was assumed to be dead during optimizations, but it is not.");
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index 8fd43ee..84aabe8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -19,6 +19,7 @@
 
   private final DexItemFactory dexItemFactory;
 
+  public final DexType enumLiteType;
   public final DexType enumLiteMapType;
   public final DexType extendableMessageType;
   public final DexType extensionDescriptorType;
@@ -41,9 +42,11 @@
   public final MethodToInvokeMembers methodToInvokeMembers;
 
   public final DexString defaultInstanceFieldName;
+  public final DexString internalValueMapFieldName;
   public final DexString dynamicMethodName;
   public final DexString findLiteExtensionByNumberName;
   public final DexString newBuilderMethodName;
+  public final DexString findValueByNumberName;
 
   public final DexString protobufPackageDescriptorPrefix;
 
@@ -58,6 +61,7 @@
     dexItemFactory = factory;
 
     // Types.
+    enumLiteType = factory.createType("Lcom/google/protobuf/Internal$EnumLite;");
     enumLiteMapType = factory.createType("Lcom/google/protobuf/Internal$EnumLiteMap;");
     extendableMessageType =
         factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableMessage;");
@@ -81,9 +85,11 @@
 
     // Names.
     defaultInstanceFieldName = factory.createString("DEFAULT_INSTANCE");
+    internalValueMapFieldName = factory.createString("internalValueMap");
     dynamicMethodName = factory.createString("dynamicMethod");
     findLiteExtensionByNumberName = factory.createString("findLiteExtensionByNumber");
     newBuilderMethodName = factory.createString("newBuilder");
+    findValueByNumberName = factory.createString("findValueByNumber");
 
     // Other names.
     protobufPackageDescriptorPrefix = factory.createString("Lcom/google/protobuf/");
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
index 78f9e6e..6cec099 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
@@ -20,6 +20,7 @@
   public final GeneratedExtensionRegistryShrinker generatedExtensionRegistryShrinker;
   public final GeneratedMessageLiteShrinker generatedMessageLiteShrinker;
   public final GeneratedMessageLiteBuilderShrinker generatedMessageLiteBuilderShrinker;
+  public final EnumLiteProtoShrinker enumProtoShrinker;
   public final ProtoReferences references;
 
   private Set<DexType> deadProtoTypes = Sets.newIdentityHashSet();
@@ -41,6 +42,7 @@
         appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking
             ? new GeneratedMessageLiteBuilderShrinker(appView, references)
             : null;
+    this.enumProtoShrinker = new EnumLiteProtoShrinker(appView, references);
     this.references = references;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 6deea80..de727c4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -625,7 +625,20 @@
             locals, stack, getCanonicalDebugPositionAtOffset(currentInstructionIndex));
     // Update the incoming state as well with precise information.
     assert incomingState.get(currentBlockIndex) != null;
-    incomingState.put(currentBlockIndex, snapshot);
+    if (isFirstFrameInBlock()) {
+      incomingState.put(currentBlockIndex, snapshot);
+    }
+  }
+
+  private boolean isFirstFrameInBlock() {
+    for (int i = currentBlockIndex; i < currentInstructionIndex; i++) {
+      CfInstruction cfInstruction = code.getInstructions().get(i);
+      if (cfInstruction.isPosition() || cfInstruction.isLabel()) {
+        continue;
+      }
+      return false;
+    }
+    return true;
   }
 
   private DexType convertUninitialized(FrameType type) {
@@ -716,16 +729,17 @@
           return localVariablesWithRegister.get(0).getLocal().type;
         }
       }
-      appView
-          .options()
-          .reporter
-          .warning(
-              new CfCodeDiagnostics(
-                  origin,
-                  method.getReference(),
-                  "Could not find phi type for register "
-                      + register
-                      + ". This is most likely due to invalid stack maps in input."));
+      // TODO(b/169346184): Delay reporting errors here due to invalid debug info until resolved.
+      // appView
+      //     .options()
+      //     .reporter
+      //     .warning(
+      //         new CfCodeDiagnostics(
+      //             origin,
+      //             method.getReference(),
+      //             "Could not find phi type for register "
+      //                 + register
+      //                 + ". This is most likely due to invalid stack maps in input."));
       return null;
     }
     if (slot.isPrecise()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 100131c..b695024 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -543,6 +543,9 @@
   }
 
   private boolean needsIRConversion(Code code, ProgramMethod method) {
+    if (options.testing.forceIRForCfToCfDesugar) {
+      return true;
+    }
     if (options.isDesugaredLibraryCompilation()) {
       // TODO(b/169035524): Create method for evaluating if a code object needs rewriting for
       // library desugaring.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index e0b6ff5..b4bd339 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -89,7 +89,8 @@
           && staticField.accessFlags.isFinal()
           && staticField.field.name == factory.enumValuesFieldName) {
         // Field $VALUES, valid, do nothing.
-      } else {
+      } else if (appView.appInfo().isFieldRead(staticField)) {
+        // Only non read static fields are valid, and they are assumed unused.
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index 869cb6e..9bc6826 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
@@ -15,6 +16,7 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -42,10 +44,40 @@
         originalMethodSignatures,
         previousLens,
         dexItemFactory);
+    assert noDuplicateEntries(fieldMap, originalFieldSignatures);
+    assert noDuplicateEntries(methodMap, originalMethodSignatures);
     this.prototypeChangesPerMethod = prototypeChangesPerMethod;
     this.unboxedEnums = unboxedEnums;
   }
 
+  private <T extends DexMember<?, ?>> boolean noDuplicateEntries(
+      Map<T, T> map, BiMap<T, T> originalSignatures) {
+    if (map.size() == originalSignatures.size()) {
+      return true;
+    }
+    IdentityHashMap<T, T> methodMapReverse = new IdentityHashMap<>();
+    IdentityHashMap<T, Set<T>> duplicate = new IdentityHashMap<>();
+    map.forEach(
+        (k, v) -> {
+          if (methodMapReverse.containsKey(v)) {
+            Set<T> dexMethods = duplicate.computeIfAbsent(v, ignored -> Sets.newIdentityHashSet());
+            dexMethods.add(methodMapReverse.get(v));
+            dexMethods.add(k);
+          } else {
+            methodMapReverse.put(v, k);
+          }
+        });
+    assert !duplicate.isEmpty();
+    StringBuilder sb = new StringBuilder();
+    sb.append("Enum unboxing has created duplicate members: \n");
+    duplicate.forEach(
+        (target, origins) -> {
+          sb.append(origins).append(" -> ").append(target).append("\n");
+        });
+    assert false : sb.toString();
+    return false;
+  }
+
   @Override
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
       RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 7d40dba..c937356 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -192,7 +192,6 @@
                   iterator, invokeMethod, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
             }
           }
-          // TODO(b/147860220): rewrite also other enum methods.
         } else if (instruction.isInvokeStatic()) {
           InvokeStatic invokeStatic = instruction.asInvokeStatic();
           DexMethod invokedMethod = invokeStatic.getInvokedMethod();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 879c909..634d74e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -145,14 +145,11 @@
     DexClass holder = appView.definitionFor(encodedMethod.holder());
     assert holder != null;
     if (encodedMethod.isInstanceInitializer()) {
-      while (holder.lookupMethod(newMethod) != null) {
-        newMethod =
-            factory.createMethod(
-                newMethod.holder,
-                factory.appendTypeToProto(
-                    newMethod.proto, relocator.getDefaultEnumUnboxingUtility()),
-                newMethod.name);
-      }
+      newMethod =
+          factory.createInstanceInitializerWithFreshProto(
+              newMethod,
+              relocator.getDefaultEnumUnboxingUtility(),
+              tryMethod -> holder.lookupMethod(tryMethod) == null);
     } else {
       int index = 0;
       while (holder.lookupMethod(newMethod) != null) {
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
index b50015b..fbaf1b8 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -54,7 +54,7 @@
     // package descriptor.
     boolean hasPinnedItem = false;
     for (DexProgramClass clazz : pkg) {
-      boolean isPinned = !appView.appInfo().isMinificationAllowed(clazz.getType());
+      boolean isPinned = !appView.appInfo().isRepackagingAllowed(clazz.getType());
       Node classNode = createNode(clazz);
       if (isPinned) {
         pinnedNodes.add(classNode);
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 6c28f2e..acb52c5 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -409,7 +409,7 @@
       AppInfoWithLiveness previous,
       DirectMappedDexApplication application,
       Set<DexType> removedClasses,
-      Collection<DexReference> additionalPinnedItems) {
+      Collection<? extends DexReference> additionalPinnedItems) {
     this(
         previous.getSyntheticItems().commitPrunedClasses(application, removedClasses),
         previous.getClassToFeatureSplitMap().withoutPrunedClasses(removedClasses),
@@ -460,7 +460,7 @@
   }
 
   private static KeepInfoCollection extendPinnedItems(
-      AppInfoWithLiveness previous, Collection<DexReference> additionalPinnedItems) {
+      AppInfoWithLiveness previous, Collection<? extends DexReference> additionalPinnedItems) {
     if (additionalPinnedItems == null || additionalPinnedItems.isEmpty()) {
       return previous.keepInfo;
     }
@@ -912,6 +912,11 @@
     return keepInfo.getInfo(reference, this).isAccessModificationAllowed(options());
   }
 
+  public boolean isRepackagingAllowed(DexType type) {
+    return options().isRepackagingEnabled()
+        && keepInfo.getClassInfo(type, this).isRepackagingAllowed(options());
+  }
+
   public boolean isPinned(DexReference reference) {
     assert checkIfObsolete();
     return keepInfo.isPinned(reference, this);
@@ -941,7 +946,7 @@
   public AppInfoWithLiveness prunedCopyFrom(
       DirectMappedDexApplication application,
       Set<DexType> removedClasses,
-      Collection<DexReference> additionalPinnedItems) {
+      Collection<? extends DexReference> additionalPinnedItems) {
     assert checkIfObsolete();
     if (!removedClasses.isEmpty()) {
       // Rebuild the hierarchy.
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
index 18ae09e..624ee95 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -11,4 +11,6 @@
   boolean isMinificationEnabled();
 
   boolean isAccessModificationEnabled();
+
+  boolean isRepackagingEnabled();
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index 58367a9..1e32804 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -33,6 +33,20 @@
     return new Joiner(this);
   }
 
+  /**
+   * True if a class may be repackaged.
+   *
+   * <p>This method requires knowledge of the global configuration as that can override the concrete
+   * value on a given item.
+   */
+  public boolean isRepackagingAllowed(GlobalKeepInfoConfiguration configuration) {
+    return configuration.isRepackagingEnabled() && internalIsRepackagingAllowed();
+  }
+
+  boolean internalIsRepackagingAllowed() {
+    return internalIsMinificationAllowed();
+  }
+
   @Override
   public boolean isTop() {
     return this.equals(top());
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 6db253e..951e2e5 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
@@ -61,13 +60,14 @@
     DirectMappedDexApplication application = appView.appInfo().app().asDirect();
     Timing timing = application.timing;
     timing.begin("Pruning application...");
-    DirectMappedDexApplication result;
     try {
-      result = removeUnused(application).build();
+      DirectMappedDexApplication.Builder builder = removeUnused(application);
+      return prunedTypes.isEmpty() && !appView.options().configurationDebugging
+          ? application
+          : builder.build();
     } finally {
       timing.end();
     }
-    return result;
   }
 
   private DirectMappedDexApplication.Builder removeUnused(DirectMappedDexApplication application) {
@@ -359,7 +359,7 @@
     return Collections.unmodifiableSet(prunedTypes);
   }
 
-  public Collection<DexReference> getMethodsToKeepForConfigurationDebugging() {
+  public Collection<DexMethod> getMethodsToKeepForConfigurationDebugging() {
     return Collections.unmodifiableCollection(methodsToKeepForConfigurationDebugging);
   }
 
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
index 4808947..d0ec93e 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
@@ -3,16 +3,22 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.tracereferences;
 
-import static com.android.tools.r8.utils.ExceptionUtils.STATUS_ERROR;
-
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.Version;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -53,7 +59,25 @@
         .forEach(provider -> tagetDescriptors.addAll(provider.getClassDescriptors()));
     for (ProgramResourceProvider provider : command.getSource()) {
       for (ProgramResource programResource : provider.getProgramResources()) {
-        tagetDescriptors.removeAll(programResource.getClassDescriptors());
+        if (programResource.getKind() == Kind.DEX) {
+          command
+              .getDiagnosticsHandler()
+              .warning(new StringDiagnostic("DEX files not fully supported"));
+          assert programResource.getClassDescriptors() == null;
+          for (DexProgramClass clazz :
+              new ApplicationReader(
+                      AndroidApp.builder()
+                          .addDexProgramData(ImmutableList.of(programResource.getBytes()))
+                          .build(),
+                      new InternalOptions(),
+                      Timing.empty())
+                  .read()
+                  .classes()) {
+            tagetDescriptors.remove(clazz.getType().toDescriptorString());
+          }
+        } else {
+          tagetDescriptors.removeAll(programResource.getClassDescriptors());
+        }
       }
     }
     Tracer tracer = new Tracer(tagetDescriptors, builder.build());
@@ -77,25 +101,20 @@
     formatter.format(result);
   }
 
-  public static void main(String... args) {
-    try {
-      TraceReferencesCommand command = TraceReferencesCommand.parse(args, Origin.root()).build();
-      if (command.isPrintHelp()) {
-        System.out.println(TraceReferencesCommandParser.USAGE_MESSAGE);
-        return;
-      }
-      if (command.isPrintVersion()) {
-        System.out.println("referencetrace " + Version.getVersionString());
-        return;
-      }
-      run(command);
-    } catch (CompilationFailedException e) {
-      System.exit(STATUS_ERROR);
-    } catch (Throwable e) {
-      System.err.println("ReferenceTrace failed with an internal error.");
-      Throwable cause = e.getCause() == null ? e : e.getCause();
-      cause.printStackTrace();
-      System.exit(STATUS_ERROR);
+  public static void run(String... args) throws CompilationFailedException {
+    TraceReferencesCommand command = TraceReferencesCommand.parse(args, Origin.root()).build();
+    if (command.isPrintHelp()) {
+      System.out.println(TraceReferencesCommandParser.USAGE_MESSAGE);
+      return;
     }
+    if (command.isPrintVersion()) {
+      System.out.println("referencetrace " + Version.getVersionString());
+      return;
+    }
+    run(command);
+  }
+
+  public static void main(String[] args) {
+    ExceptionUtils.withMainProgramHandler(() -> run(args));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 0164b4e..2bbe3fe 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -576,7 +576,7 @@
                         if (featureSplitConfiguration != null) {
                           DexType type = dexItemFactory.createType(classDescriptor);
                           FeatureSplit featureSplit = classToFeatureSplitMap.getFeatureSplit(type);
-                          if (featureSplit != null) {
+                          if (featureSplit != null && !featureSplit.isBase()) {
                             return featureSplitArchiveOutputStreams.get(featureSplit);
                           }
                         }
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 a7481b7..15183a1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -171,6 +171,7 @@
     protoShrinking.enableGeneratedMessageLiteShrinking = true;
     protoShrinking.enableGeneratedMessageLiteBuilderShrinking = true;
     protoShrinking.enableGeneratedExtensionRegistryShrinking = true;
+    protoShrinking.enableEnumLiteProtoShrinking = true;
   }
 
   void disableAllOptimizations() {
@@ -530,6 +531,12 @@
     return isMinifying();
   }
 
+  @Override
+  public boolean isRepackagingEnabled() {
+    return proguardConfiguration.getPackageObfuscationMode().isSome()
+        && (isShrinking() || isMinifying());
+  }
+
   /**
    * If any non-static class merging is enabled, information about types referred to by instanceOf
    * and check cast instructions needs to be collected.
@@ -1107,6 +1114,10 @@
     public boolean isRepackageClasses() {
       return this == REPACKAGE;
     }
+
+    public boolean isSome() {
+      return !isNone();
+    }
   }
 
   public static class OutlineOptions {
@@ -1176,11 +1187,13 @@
     public boolean enableGeneratedMessageLiteShrinking = false;
     public boolean enableGeneratedMessageLiteBuilderShrinking = false;
     public boolean traverseOneOfAndRepeatedProtoFields = false;
+    public boolean enableEnumLiteProtoShrinking = false;
 
     public boolean isProtoShrinkingEnabled() {
       return enableGeneratedExtensionRegistryShrinking
           || enableGeneratedMessageLiteShrinking
-          || enableGeneratedMessageLiteBuilderShrinking;
+          || enableGeneratedMessageLiteBuilderShrinking
+          || enableEnumLiteProtoShrinking;
     }
   }
 
@@ -1256,6 +1269,8 @@
     public boolean assertConsistentRenamingOfSignature = false;
     public boolean allowStaticInterfaceMethodsForPreNApiLevel = false;
     public int verificationSizeLimitInBytesOverride = -1;
+    public boolean forceIRForCfToCfDesugar =
+        System.getProperty("com.android.tools.r8.forceIRForCfToCfDesugar") != null;
 
     // Flag to allow processing of resources in D8. A data resource consumer still needs to be
     // specified.
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingAction.java b/src/main/java/com/android/tools/r8/utils/ThrowingAction.java
new file mode 100644
index 0000000..d5b4fa8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingAction.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2018, 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.utils;
+
+@FunctionalInterface
+public interface ThrowingAction<E extends Throwable> {
+  void execute() throws E;
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java
index ad65701..cae7430 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java
@@ -19,6 +19,22 @@
     return backing.getOrDefault(key, value);
   }
 
+  public Map<K, V> getForwardMap() {
+    return backing;
+  }
+
+  public Set<K> keySet() {
+    return backing.keySet();
+  }
+
+  public boolean hasKey(K key) {
+    return backing.containsKey(key);
+  }
+
+  public boolean hasValue(V value) {
+    return inverse.containsKey(value);
+  }
+
   public Set<K> getKeys(V value) {
     return inverse.getOrDefault(value, Collections.emptySet());
   }
diff --git a/src/main/keep-applymapping.txt b/src/main/keep-applymapping.txt
deleted file mode 100644
index c404d21..0000000
--- a/src/main/keep-applymapping.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright (c) 2019, 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.
-
-# TODO(b/133091438,b/139344231) These rules are needed for applymapping but should be able to be
-# removed when we have --classpath.
--keepclassmembers,allowobfuscation class com.android.tools.r8.ir.optimize.MemberPoolCollection {
-  *** buildForHierarchy(...);
-}
--keepclassmembers,allowobfuscation class com.android.tools.r8.jar.CfApplicationWriter {
-  void write(com.android.tools.r8.ClassFileConsumer,java.util.concurrent.ExecutorService);
-  void writeApplication(com.android.tools.r8.ClassFileConsumer,java.util.concurrent.ExecutorService);
-}
--keep class com.android.tools.r8.BaseCommand {
-  com.android.tools.r8.utils.AndroidApp getInputApp();
-}
-
-# Obfuscating the members below can result in naming-conflicts so just keep them.
--keep class com.android.tools.r8.joptsimple.OptionDescriptor {
-  java.lang.String argumentDescription();
-}
diff --git a/src/test/examplesJava11/com/android/tools/r8/NeverClassInline.java b/src/test/examplesJava11/com/android/tools/r8/NeverClassInline.java
new file mode 100644
index 0000000..19c4504
--- /dev/null
+++ b/src/test/examplesJava11/com/android/tools/r8/NeverClassInline.java
@@ -0,0 +1,7 @@
+// 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;
+
+public @interface NeverClassInline {}
diff --git a/src/test/examplesJava11/nesthostexample/NeverInline.java b/src/test/examplesJava11/com/android/tools/r8/NeverInline.java
similarity index 75%
rename from src/test/examplesJava11/nesthostexample/NeverInline.java
rename to src/test/examplesJava11/com/android/tools/r8/NeverInline.java
index d800658..cd01ce1 100644
--- a/src/test/examplesJava11/nesthostexample/NeverInline.java
+++ b/src/test/examplesJava11/com/android/tools/r8/NeverInline.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// 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 nesthostexample;
+package com.android.tools.r8;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Target;
diff --git a/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging.java b/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging.java
new file mode 100644
index 0000000..2362165
--- /dev/null
+++ b/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging.java
@@ -0,0 +1,43 @@
+// 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 horizontalclassmerging;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+public class BasicNestHostHorizontalClassMerging {
+  // Prevent merging with BasicNestHostHorizontalClassMerging2.
+  private String name;
+
+  private BasicNestHostHorizontalClassMerging(String name) {
+    this.name = name;
+  }
+
+  @NeverInline
+  private void print(String v) {
+    System.out.println(name + ": " + v);
+  }
+
+  public static void main(String[] args) {
+    BasicNestHostHorizontalClassMerging host = new BasicNestHostHorizontalClassMerging("1");
+    new A(host);
+    new B(host);
+    BasicNestHostHorizontalClassMerging2.main(args);
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A(BasicNestHostHorizontalClassMerging parent) {
+      parent.print("a");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B(BasicNestHostHorizontalClassMerging parent) {
+      parent.print("b");
+    }
+  }
+}
diff --git a/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging2.java b/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging2.java
new file mode 100644
index 0000000..78718e1
--- /dev/null
+++ b/src/test/examplesJava11/horizontalclassmerging/BasicNestHostHorizontalClassMerging2.java
@@ -0,0 +1,35 @@
+// 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 horizontalclassmerging;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+public class BasicNestHostHorizontalClassMerging2 {
+  @NeverInline
+  public static void main(String[] args) {
+    new A();
+    new B();
+  }
+
+  @NeverInline
+  private static void print(String v) {
+    System.out.println("2: " + v);
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      print("a");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B() {
+      print("b");
+    }
+  }
+}
diff --git a/src/test/examplesJava11/nesthostexample/BasicNestHostClassMerging.java b/src/test/examplesJava11/nesthostexample/BasicNestHostClassMerging.java
index 6e511a0..184c201 100644
--- a/src/test/examplesJava11/nesthostexample/BasicNestHostClassMerging.java
+++ b/src/test/examplesJava11/nesthostexample/BasicNestHostClassMerging.java
@@ -4,6 +4,8 @@
 
 package nesthostexample;
 
+import com.android.tools.r8.NeverInline;
+
 public class BasicNestHostClassMerging {
 
   private String field = "Outer";
diff --git a/src/test/examplesJava11/nesthostexample/BasicNestHostTreePruning.java b/src/test/examplesJava11/nesthostexample/BasicNestHostTreePruning.java
index eb2b3bf..484c81a 100644
--- a/src/test/examplesJava11/nesthostexample/BasicNestHostTreePruning.java
+++ b/src/test/examplesJava11/nesthostexample/BasicNestHostTreePruning.java
@@ -4,6 +4,8 @@
 
 package nesthostexample;
 
+import com.android.tools.r8.NeverInline;
+
 public class BasicNestHostTreePruning {
 
   private String field = System.currentTimeMillis() >= 0 ? "NotPruned" : "Dead";
diff --git a/src/test/examplesJava11/nesthostexample/NestHostInliningSubclasses.java b/src/test/examplesJava11/nesthostexample/NestHostInliningSubclasses.java
index a6283a0..4221307 100644
--- a/src/test/examplesJava11/nesthostexample/NestHostInliningSubclasses.java
+++ b/src/test/examplesJava11/nesthostexample/NestHostInliningSubclasses.java
@@ -4,6 +4,8 @@
 
 package nesthostexample;
 
+import com.android.tools.r8.NeverInline;
+
 public class NestHostInliningSubclasses {
 
   public static class InnerWithPrivAccess extends NestHostInlining.InnerWithPrivAccess {
diff --git a/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java b/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java
index dd76928..cdbe725 100644
--- a/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java
+++ b/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java
@@ -4,6 +4,8 @@
 
 package nesthostexample;
 
+import com.android.tools.r8.NeverInline;
+
 public class NestPvtMethodCallInlined {
 
   public static class Inner {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 4e8d99e..7c68c50 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -101,6 +101,7 @@
   @Override
   @Test
   public void lambdaDesugaring() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
@@ -120,6 +121,7 @@
 
   @Test
   public void testMultipleInterfacesLambdaOutValue() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     // We can only remove trivial check casts for the lambda objects if we keep track all the
     // multiple interfaces we additionally specified for the lambdas
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -141,6 +143,7 @@
   @Test
   @IgnoreIfVmOlderThan(Version.V7_0_0)
   public void lambdaDesugaringWithDefaultMethods() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(AndroidApiLevel.N)
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
@@ -161,6 +164,7 @@
   @Override
   @Test
   public void lambdaDesugaringNPlus() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
@@ -185,6 +189,7 @@
   @Test
   @IgnoreIfVmOlderThan(Version.V7_0_0)
   public void lambdaDesugaringNPlusWithDefaultMethods() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
         .withMinApiLevel(AndroidApiLevel.N)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
index 9ac01a0..8edbd69 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -51,6 +51,7 @@
 
   @Test
   public void invokeCustomWithShrinking() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     test("invokecustom-with-shrinking", "invokecustom", "InvokeCustom")
         .withMinApiLevel(AndroidApiLevel.P.getLevel())
         .withBuilderTransformation(builder ->
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 6568713..c72a3ea 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -53,8 +53,9 @@
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 
-public abstract class RunExamplesAndroidOTest
-      <B extends BaseCommand.Builder<? extends BaseCommand, B>> {
+public abstract class RunExamplesAndroidOTest<
+        B extends BaseCommand.Builder<? extends BaseCommand, B>>
+    extends TestBase {
   static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR;
 
   abstract class TestRunner<C extends TestRunner<C>> {
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index 661c815..d06d976 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -45,8 +45,9 @@
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 
-public abstract class RunExamplesAndroidPTest
-      <B extends BaseCommand.Builder<? extends BaseCommand, B>> {
+public abstract class RunExamplesAndroidPTest<
+        B extends BaseCommand.Builder<? extends BaseCommand, B>>
+    extends TestBase {
   static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_ANDROID_P_BUILD_DIR;
 
   abstract class TestRunner<C extends TestRunner<C>> {
diff --git a/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java b/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java
index 7a679e5..7a76f4a 100644
--- a/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java
+++ b/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java
@@ -39,8 +39,6 @@
               diagnotics.assertNoErrors();
               diagnotics.assertInfoThatMatches(
                   diagnosticMessage(containsString("invalid locals information")));
-              diagnotics.assertAllWarningsMatch(
-                  diagnosticMessage(containsString("Could not find phi type for register 14")));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index b76e08f..c3c51de 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -8,6 +8,7 @@
 import static com.google.common.collect.Lists.cartesianProduct;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.DataResourceProvider.Visitor;
@@ -71,9 +72,12 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.PreloadedClassFileProvider;
+import com.android.tools.r8.utils.ReflectiveBuildPathUtils;
+import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesClass;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.ThrowingAction;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -118,6 +122,7 @@
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Rule;
+import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
@@ -351,6 +356,8 @@
   // Actually running r8.jar in a forked process.
   private static final boolean RUN_R8_JAR = System.getProperty("run_r8_jar") != null;
 
+  @Rule public ExpectedException thrown = ExpectedException.none();
+
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
@@ -1649,6 +1656,10 @@
     return clazz.getTypeName();
   }
 
+  public static String examplesTypeName(Class<? extends ExamplesClass> clazz) throws Exception {
+    return ReflectiveBuildPathUtils.resolveClassName(clazz);
+  }
+
   public static AndroidApiLevel apiLevelWithDefaultInterfaceMethodsSupport() {
     return AndroidApiLevel.N;
   }
@@ -1688,4 +1699,32 @@
         .compile()
         .writeToZip();
   }
+
+  public static <E extends Throwable> void assertThrowsWithHorizontalClassMerging(
+      ThrowingAction<E> action) throws E {
+    try {
+      action.execute();
+      if (isHorizontalClassMergingEnabled()) {
+        fail();
+      }
+    } catch (Throwable throwable) {
+      if (!isHorizontalClassMergingEnabled()) {
+        throw throwable;
+      }
+    }
+  }
+
+  public void expectThrowsWithHorizontalClassMerging() {
+    expectThrowsWithHorizontalClassMergingIf(true);
+  }
+
+  public void expectThrowsWithHorizontalClassMergingIf(boolean condition) {
+    if (isHorizontalClassMergingEnabled() && condition) {
+      thrown.expect(Throwable.class);
+    }
+  }
+
+  private static boolean isHorizontalClassMergingEnabled() {
+    return System.getProperty("com.android.tools.r8.horizontalClassMerging") != null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBaseBuilder.java b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
index 1fe43fb..1e75de5 100644
--- a/src/test/java/com/android/tools/r8/TestBaseBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.ReflectiveBuildPathUtils;
+import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesRootPackage;
 import com.google.common.collect.ImmutableMap;
 import java.nio.file.Path;
 import java.util.Arrays;
@@ -54,6 +56,12 @@
     return self();
   }
 
+  public T addExamplesProgramFiles(Class<? extends ExamplesRootPackage> rootPackage)
+      throws Exception {
+    Collection<Path> files = ReflectiveBuildPathUtils.allClassFiles(rootPackage);
+    return addProgramFiles(files);
+  }
+
   public T addLibraryProvider(ClassFileResourceProvider provider) {
     builder.addLibraryResourceProvider(provider);
     return self();
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e6fd9e0..9740354 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -790,6 +790,30 @@
     return Files.exists(path);
   }
 
+  public static boolean shouldHaveAndroidJar(AndroidApiLevel apiLevel) {
+    switch (apiLevel) {
+      case B_1_1:
+      case C:
+      case D:
+      case E:
+      case E_0_1:
+      case E_MR1:
+      case F:
+      case G:
+      case G_MR1:
+      case H:
+      case H_MR1:
+      case H_MR2:
+      case J:
+      case J_MR1:
+      case J_MR2:
+      case K_WATCH:
+        return false;
+      default:
+        return true;
+    }
+  }
+
   public static Path getAndroidJar(AndroidApiLevel apiLevel) {
     Path path = getAndroidJarPath(apiLevel);
     assert Files.exists(path)
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
index 7756299..bf103e4 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -47,6 +47,7 @@
 
   @Test
   public void testStaticMethodRelaxation() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     String expectedOutput =
         StringUtils.lines(
             "A::baz()",
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 76ebd58..09bf5e1 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -64,6 +64,7 @@
    */
   @Test
   public void test_bridgeTargetInBase_differentBridges() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     JasminBuilder jasminBuilder = new JasminBuilder();
 
     ClassBuilder absCls = jasminBuilder.addClass("AbsCls");
@@ -185,6 +186,7 @@
    */
   @Test
   public void test_bridgeTargetInBase_bridgeAndNonBridge() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     JasminBuilder jasminBuilder = new JasminBuilder();
 
     ClassBuilder baseCls = jasminBuilder.addClass("Base");
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index 32fa0b8..aed9abc 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -39,6 +39,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(RemoveVisibilityBridgeMethodsTest.class)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
index c9f7d21..fc125dc 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
@@ -38,6 +38,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
         .addInnerClasses(BridgeHoistingAccessibilityTestClasses.class)
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
index d764895..2665473 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
@@ -34,6 +34,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class, A.class, B3.class, B4.class)
         .addProgramClassFileData(
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index 1bde24a..4baa16c 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -71,10 +71,13 @@
 
   @BeforeClass
   public static void beforeAll() throws Exception {
-    if (data().stream().count() > 0) {
-      r8R8Debug = compileR8(CompilationMode.DEBUG);
-      r8R8Release = compileR8(CompilationMode.RELEASE);
-    }
+    assertThrowsWithHorizontalClassMerging(
+        () -> {
+          if (data().stream().count() > 0) {
+            r8R8Debug = compileR8(CompilationMode.DEBUG);
+            r8R8Release = compileR8(CompilationMode.RELEASE);
+          }
+        });
   }
 
   @Parameters(name = "{0}")
@@ -120,6 +123,7 @@
 
   @Test
   public void testRetrace() throws IOException {
+    expectThrowsWithHorizontalClassMerging();
     ProcessResult processResult =
         ToolHelper.runProcess(
             new ProcessBuilder()
@@ -205,6 +209,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     Path helloJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
     ProcessResult runResult = ToolHelper.runJava(helloJar, "hello.Hello");
     assertEquals(0, runResult.exitCode);
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
index 722e4f6..1c7a809 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
@@ -73,6 +73,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     // Run hello.jar to ensure it exists and is valid.
     Path hello = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
     ProcessResult runHello = ToolHelper.runJava(hello, "hello.Hello");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassTest.java
new file mode 100644
index 0000000..5edc29f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassTest.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.classmerging.horizontal.NestClassTest.R.horizontalclassmerging.BasicNestHostHorizontalClassMerging;
+import com.android.tools.r8.classmerging.horizontal.NestClassTest.R.horizontalclassmerging.BasicNestHostHorizontalClassMerging2;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesClass;
+import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesJava11RootPackage;
+import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesPackage;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runners.Parameterized;
+
+public class NestClassTest extends HorizontalClassMergingTestBase {
+  public static class R extends ExamplesJava11RootPackage {
+    public static class horizontalclassmerging extends ExamplesPackage {
+      public static class BasicNestHostHorizontalClassMerging extends ExamplesClass {
+        public static class A extends ExamplesClass {}
+
+        public static class B extends ExamplesClass {}
+      }
+
+      public static class BasicNestHostHorizontalClassMerging2 extends ExamplesClass {
+        public static class A extends ExamplesClass {}
+
+        public static class B extends ExamplesClass {}
+      }
+    }
+  }
+
+  public NestClassTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build(),
+        BooleanUtils.values());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addKeepMainRule(examplesTypeName(BasicNestHostHorizontalClassMerging.class))
+        .addExamplesProgramFiles(R.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .compile()
+        .run(parameters.getRuntime(), examplesTypeName(BasicNestHostHorizontalClassMerging.class))
+        .assertSuccessWithOutputLines("1: a", "1: b", "2: a", "2: b")
+        .inspect(
+            codeInspector -> {
+              ClassSubject class1A =
+                  codeInspector.clazz(
+                      examplesTypeName(BasicNestHostHorizontalClassMerging.A.class));
+              ClassSubject class2A =
+                  codeInspector.clazz(
+                      examplesTypeName(BasicNestHostHorizontalClassMerging2.A.class));
+              ClassSubject class1 =
+                  codeInspector.clazz(examplesTypeName(BasicNestHostHorizontalClassMerging.class));
+              ClassSubject class2 =
+                  codeInspector.clazz(examplesTypeName(BasicNestHostHorizontalClassMerging2.class));
+              assertThat(class1, isPresent());
+              assertThat(class2, isPresent());
+              assertThat(class1A, isPresent());
+              assertThat(class2A, isPresent());
+
+              MethodSubject printClass1MethodSubject =
+                  class1.method("void", "print", String.class.getTypeName());
+              assertThat(printClass1MethodSubject, isPresent());
+              assertThat(printClass1MethodSubject, isPrivate());
+
+              MethodSubject printClass2MethodSubject =
+                  class2.method("void", "print", String.class.getTypeName());
+              assertThat(printClass2MethodSubject, isPresent());
+              assertThat(printClass2MethodSubject, isPrivate());
+              assertThat(printClass2MethodSubject, isStatic());
+
+              assertThat(
+                  codeInspector.clazz(
+                      examplesTypeName(BasicNestHostHorizontalClassMerging.B.class)),
+                  notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(
+                  codeInspector.clazz(
+                      examplesTypeName(BasicNestHostHorizontalClassMerging2.B.class)),
+                  notIf(isPresent(), enableHorizontalClassMerging));
+
+              // TODO(b/165517236): Explicitly check 1.B is merged into 1.A, and 2.B into 2.A.
+            });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerCollisionTest.java
new file mode 100644
index 0000000..b603f66
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerCollisionTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class TreeFixerCollisionTest extends HorizontalClassMergingTestBase {
+  public TreeFixerCollisionTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "print a: foo a", "print b: foo b", "print d: foo a", "print e: foo b")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(Group2.class), isPresent());
+              assertThat(codeInspector.clazz(C.class), isPresent());
+              assertThat(codeInspector.clazz(D.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(E.class), notIf(isPresent(), enableHorizontalClassMerging));
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    @NeverInline
+    public String foo() {
+      return "foo a";
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+
+    @NeverInline
+    public String foo() {
+      return "foo b";
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  public static class C {
+    @NeverInline
+    public void print(A a) {
+      System.out.println("print a: " + a.foo());
+    }
+
+    @NeverInline
+    public void print(B a) {
+      System.out.println("print b: " + a.foo());
+    }
+  }
+
+  @NoVerticalClassMerging
+  @NoHorizontalClassMerging
+  public static class Group2 {}
+
+  @NeverClassInline
+  public static class D extends Group2 {
+    @NeverInline
+    public void print(A a) {
+      System.out.println("print d: " + a.foo());
+    }
+  }
+
+  @NeverClassInline
+  public static class E extends Group2 {
+    @NeverInline
+    public void print(B b) {
+      System.out.println("print e: " + b.foo());
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B();
+      C c = new C();
+      c.print(a);
+      c.print(b);
+      new D().print(a);
+      new E().print(b);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java
new file mode 100644
index 0000000..a5c5555
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.TreeFixerCollisionTest.A;
+import com.android.tools.r8.classmerging.horizontal.TreeFixerCollisionTest.B;
+import com.android.tools.r8.classmerging.horizontal.TreeFixerCollisionTest.C;
+import com.android.tools.r8.classmerging.horizontal.TreeFixerCollisionTest.Main;
+import org.junit.Test;
+
+public class TreeFixerConstructorCollisionTest extends HorizontalClassMergingTestBase {
+  public TreeFixerConstructorCollisionTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "constructor a",
+            "constructor 2 a",
+            "constructor b",
+            "constructor 2 b",
+            "constructor c a: foo a: c1",
+            "constructor c b: foo b: c2")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(C.class), isPresent());
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("constructor a");
+    }
+
+    public A(A a) {
+      System.out.println("constructor 2 a");
+    }
+
+    @NeverInline
+    public String foo(String v) {
+      return "foo a: " + v;
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B() {
+      System.out.println("constructor b");
+    }
+
+    public B(B b) {
+      System.out.println("constructor 2 b");
+    }
+
+    @NeverInline
+    public String foo(String v) {
+      return "foo b: " + v;
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  public static class C {
+    public C(A a, String v) {
+      System.out.println("constructor c a: " + a.foo(v));
+    }
+
+    public C(B b, String v) {
+      System.out.println("constructor c b: " + b.foo(v));
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      a = new A(a);
+      B b = new B();
+      b = new B(b);
+      new C(a, "c1");
+      new C(b, "c2");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
new file mode 100644
index 0000000..f1301a0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceFixedCollisionTest.B;
+import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceImplementedByParentTest.I;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+
+/**
+ * This test creates a conflict between Parent#foo(B) and C#foo(A), because A and B are merged.
+ * Normally C#foo(A) would be renamed, but because it is an interface method it should not be
+ * changed.
+ */
+public class TreeFixerInterfaceCollisionTest extends HorizontalClassMergingTestBase {
+  public TreeFixerInterfaceCollisionTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .noMinification()
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("print a: c", "print b: parent", "print a: e")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+
+              ClassSubject parentClassSubject = codeInspector.clazz(Parent.class);
+              assertThat(parentClassSubject, isPresent());
+              if (enableHorizontalClassMerging) {
+                // Parent#foo is renamed to Parent#foo1 to prevent collision.
+                assertThat(parentClassSubject.uniqueMethodWithName("foo$1"), isPresent());
+              }
+
+              ClassSubject cClassSubject = codeInspector.clazz(C.class);
+              assertThat(cClassSubject, isPresent());
+              // C#foo is not renamed to match interface.
+              assertThat(cClassSubject.uniqueMethodWithName("foo"), isPresent());
+
+              ClassSubject iClassSubject = codeInspector.clazz(I.class);
+              assertThat(iClassSubject, isPresent());
+              assertThat(iClassSubject.uniqueMethodWithFinalName("foo"), isPresent());
+            });
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public interface I {
+    @NeverInline
+    void foo(A a);
+  }
+
+  @NoVerticalClassMerging
+  @NoHorizontalClassMerging
+  public static class Parent {
+    @NeverInline
+    void foo(B b) {
+      b.print("parent");
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+    @NeverInline
+    public void print(String v) {
+      System.out.println("print a: " + v);
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    @NeverInline
+    public void print(String v) {
+      System.out.println("print b: " + v);
+    }
+  }
+
+  public static class C extends Parent implements I {
+    @Override
+    @NeverInline
+    public void foo(A a) {
+      a.print("c");
+    }
+  }
+
+  @NeverClassInline
+  public static class E implements I {
+    @NeverInline
+    public void foo(A a) {
+      a.print("e");
+    }
+  }
+
+  public static class Main {
+    @NeverInline
+    public static void fooI(I i, A a) {
+      i.foo(a);
+    }
+
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B();
+      C c = new C();
+      I i = c;
+      fooI(i, a);
+      c.foo(b);
+      fooI(new E(), a);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
new file mode 100644
index 0000000..2f6895a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceCollisionTest.C;
+import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceImplementedByParentTest.B;
+import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceImplementedByParentTest.E;
+import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceImplementedByParentTest.I;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+
+/**
+ * This test creates a conflict between Parent#foo(B) and C#foo(A), because A and B are merged.
+ * Normally C#foo(A) would be renamed, but because it is an interface method it should not be
+ * changed.
+ */
+public class TreeFixerInterfaceFixedCollisionTest extends HorizontalClassMergingTestBase {
+  public TreeFixerInterfaceFixedCollisionTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .noMinification()
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("print a: parent", "print b: c", "print b: e")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+
+              ClassSubject parentClassSubject = codeInspector.clazz(Parent.class);
+              assertThat(parentClassSubject, isPresent());
+              if (enableHorizontalClassMerging) {
+                // Parent#foo is renamed to Parent#foo1 to prevent collision.
+                assertThat(parentClassSubject.uniqueMethodWithFinalName("foo$1"), isPresent());
+              }
+
+              ClassSubject cClassSubject = codeInspector.clazz(C.class);
+              assertThat(cClassSubject, isPresent());
+              // C#foo is not renamed to match interface.
+              assertThat(cClassSubject.uniqueMethodWithName("foo"), isPresent());
+
+              ClassSubject iClassSubject = codeInspector.clazz(I.class);
+              assertThat(iClassSubject, isPresent());
+              assertThat(iClassSubject.uniqueMethodWithFinalName("foo"), isPresent());
+            });
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public interface I {
+    @NeverInline
+    void foo(B b);
+  }
+
+  @NoVerticalClassMerging
+  @NoHorizontalClassMerging
+  public static class Parent {
+    @NeverInline
+    void foo(A a) {
+      a.print("parent");
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+    @NeverInline
+    public void print(String v) {
+      System.out.println("print a: " + v);
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    @NeverInline
+    public void print(String v) {
+      System.out.println("print b: " + v);
+    }
+  }
+
+  public static class C extends Parent implements I {
+    @Override
+    @NeverInline
+    public void foo(B b) {
+      b.print("c");
+    }
+  }
+
+  @NeverClassInline
+  public static class E implements I {
+    @NeverInline
+    public void foo(B b) {
+      b.print("e");
+    }
+  }
+
+  public static class Main {
+    @NeverInline
+    public static void fooI(I i, B b) {
+      i.foo(b);
+    }
+
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B();
+      C c = new C();
+      I i = c;
+      c.foo(a);
+      fooI(i, b);
+      fooI(new E(), b);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceImplementedByParentTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceImplementedByParentTest.java
new file mode 100644
index 0000000..b6a320c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceImplementedByParentTest.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+
+/**
+ * This test creates a conflict between Parent#foo(B) and C#foo(A), because A and B are merged.
+ * Normally C#foo(A) would be renamed, but because it is an interface method it should not be
+ * changed.
+ */
+public class TreeFixerInterfaceImplementedByParentTest extends HorizontalClassMergingTestBase {
+  public TreeFixerInterfaceImplementedByParentTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .noMinification()
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("print a: parent", "print b: i", "print b: e")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+
+              ClassSubject parentClassSubject = codeInspector.clazz(Parent.class);
+              assertThat(parentClassSubject, isPresent());
+              // Parent #foo(A) is kept as is.
+              if (enableHorizontalClassMerging) {
+                // Parent#foo(B) is renamed to Parent#foo1 to prevent collision.
+                assertThat(parentClassSubject.uniqueMethodWithFinalName("foo"), isPresent());
+                assertThat(parentClassSubject.uniqueMethodWithFinalName("foo$1"), isPresent());
+              }
+
+              assertThat(codeInspector.clazz(C.class), isPresent());
+
+              ClassSubject iClassSubject = codeInspector.clazz(I.class);
+              assertThat(iClassSubject, isPresent());
+              assertThat(iClassSubject.uniqueMethodWithFinalName("foo"), isPresent());
+            });
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public interface I {
+    @NeverInline
+    void foo(B b);
+  }
+
+  @NoVerticalClassMerging
+  @NoHorizontalClassMerging
+  public static class Parent {
+    @NeverInline
+    public void foo(B b) {
+      b.print("i");
+    }
+
+    @NeverInline
+    public void foo(A a) {
+      a.print("parent");
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+    @NeverInline
+    public void print(String v) {
+      System.out.println("print a: " + v);
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    @NeverInline
+    public void print(String v) {
+      System.out.println("print b: " + v);
+    }
+  }
+
+  @NeverClassInline
+  public static class C extends Parent implements I {}
+
+  @NeverClassInline
+  public static class E implements I {
+    @NeverInline
+    public void foo(B b) {
+      b.print("e");
+    }
+  }
+
+  public static class Main {
+    @NeverInline
+    public static void fooI(I i, B b) {
+      i.foo(b);
+    }
+
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B();
+      C c = new C();
+      I i = c;
+      c.foo(a);
+      fooI(i, b);
+      fooI(new E(), b);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java
new file mode 100644
index 0000000..e5c6561
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+
+public class TreeFixerSubClassCollisionTest extends HorizontalClassMergingTestBase {
+  public TreeFixerSubClassCollisionTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .noMinification()
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("print a: foo c a", "print b: foo c b", "print b: foo d b")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+
+              ClassSubject cClassSubject = codeInspector.clazz(C.class);
+              assertThat(cClassSubject, isPresent());
+              // C#foo(B) is renamed to C#foo$2(A) to not collide with D#foo$1(A).
+              if (enableHorizontalClassMerging) {
+                assertThat(cClassSubject.uniqueMethodWithFinalName("foo"), isPresent());
+                assertThat(cClassSubject.uniqueMethodWithFinalName("foo$2"), isPresent());
+              }
+
+              ClassSubject dClassSubject = codeInspector.clazz(D.class);
+              assertThat(dClassSubject, isPresent());
+              assertThat(dClassSubject.uniqueMethodWithFinalName("foo$1"), isPresent());
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    @NeverInline
+    public void print(String v) {
+      System.out.println("print a: " + v);
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    @NeverInline
+    public void print(String v) {
+      System.out.println("print b: " + v);
+    }
+  }
+
+  @NoHorizontalClassMerging
+  @NeverClassInline
+  public static class C {
+    @NeverInline
+    public void foo(A a) {
+      a.print("foo c a");
+    }
+
+    @NeverInline
+    public void foo(B b) {
+      b.print("foo c b");
+    }
+  }
+
+  @NoVerticalClassMerging
+  @NeverClassInline
+  public static class D extends C {
+    @NeverInline
+    public void foo$1(B b) {
+      b.print("foo d b");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B();
+      C c = new C();
+      c.foo(a);
+      c.foo(b);
+      D d = new D();
+      d.foo$1(b);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMerged.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMergedTest.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMerged.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMergedTest.java
index 19c458f..3d29697 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMerged.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMergedTest.java
@@ -14,8 +14,9 @@
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
 
-public class VirtualMethodSuperMerged extends HorizontalClassMergingTestBase {
-  public VirtualMethodSuperMerged(TestParameters parameters, boolean enableHorizontalClassMerging) {
+public class VirtualMethodSuperMergedTest extends HorizontalClassMergingTestBase {
+  public VirtualMethodSuperMergedTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
     super(parameters, enableHorizontalClassMerging);
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
index 9d545e5..937953a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
@@ -37,6 +37,7 @@
 
   @Test
   public void testStaticClassIsRemoved() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     CodeInspector inspector =
         testForR8(parameters.getBackend())
             .addInnerClasses(StaticClassMergerVisibilityTest.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
index 505aa07..156836d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
@@ -30,6 +30,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(IncorrectRewritingOfInvokeSuperTest.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 2411076..4c5d998 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -125,6 +125,7 @@
 
   @Test
   public void testClassesHaveBeenMerged() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     runR8(EXAMPLE_KEEP, this::configure);
     // GenericInterface should be merged into GenericInterfaceImpl.
     for (String candidate : CAN_BE_MERGED) {
@@ -410,6 +411,7 @@
 
   @Test
   public void testMethodCollision() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     String main = "classmerging.MethodCollisionTest";
     Path[] programFiles =
         new Path[] {
@@ -1009,6 +1011,7 @@
 
   @Test
   public void testSyntheticBridgeSignatures() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     // Try both with and without inlining. If the bridge signatures are not updated properly, and
     // inlining is enabled, then there can be issues with our inlining invariants regarding the
     // outermost caller. If inlining is disabled, there is a risk that the methods will end up
@@ -1082,6 +1085,7 @@
   // should be updated, and class A should be removed entirely.
   @Test
   public void testExceptionTables() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     String main = "classmerging.ExceptionTest";
     Path[] programFiles =
         new Path[] {
@@ -1218,6 +1222,7 @@
 
   @Test
   public void testNoIllegalClassAccessWithAccessModifications() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     // If access modifications are allowed then SimpleInterface should be merged into
     // SimpleInterfaceImpl.
     String main = "classmerging.SimpleInterfaceAccessTest";
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
index 99b1316..380703c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -6,10 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.L8Command;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -139,18 +136,8 @@
           .assertSuccessWithOutput(expectedOutput());
     } else {
       // Build the desugared library in class file format.
-      Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs.jar");
-      L8Command.Builder l8Builder =
-          L8Command.builder()
-              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-              .addProgramFiles(ToolHelper.getDesugarJDKLibs())
-              .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
-              .setMode(CompilationMode.DEBUG)
-              .addDesugaredLibraryConfiguration(
-                  StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
-              .setMinApiLevel(parameters.getApiLevel().getLevel())
-              .setOutput(desugaredLib, OutputMode.ClassFile);
-      ToolHelper.runL8(l8Builder.build(), this::configurationForLibraryCompilation);
+      Path desugaredLib =
+          getDesugaredLibraryInCF(parameters, this::configurationForLibraryCompilation);
 
       // Run on the JVM with desuagred library on classpath.
       testForJvm()
@@ -163,6 +150,8 @@
 
   @Test
   public void testBufferedReaderD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     Assume.assumeTrue(parameters.getRuntime().isDex());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
@@ -191,6 +180,8 @@
 
   @Test
   public void testBufferedReaderR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     Assume.assumeTrue(parameters.getRuntime().isDex());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
index ae709f3..0fcb214 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
@@ -4,13 +4,18 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -28,7 +33,8 @@
   @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
   }
 
   public ConcurrentHashMapSubclassTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
@@ -38,6 +44,9 @@
 
   @Test
   public void testCustomCollectionD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
+    Assume.assumeTrue(parameters.getRuntime().isDex());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addInnerClasses(ConcurrentHashMapSubclassTest.class)
@@ -54,7 +63,48 @@
   }
 
   @Test
+  public void testCustomCollectionD8CF() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Path jar =
+        testForD8(Backend.CF)
+            .addInnerClasses(ConcurrentHashMapSubclassTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .writeToZip();
+    String desugaredLibraryKeepRules = "";
+    if (shrinkDesugaredLibrary && keepRuleConsumer.get() != null) {
+      // Collection keep rules is only implemented in the DEX writer.
+      assertEquals(0, keepRuleConsumer.get().length());
+      desugaredLibraryKeepRules = "-keep class * { *; }";
+    }
+    if (parameters.getRuntime().isDex()) {
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary,
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    } else {
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    }
+  }
+
+  @Test
   public void testCustomCollectionR8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(Backend.DEX)
         .addInnerClasses(ConcurrentHashMapSubclassTest.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
index 8334f1d..0a3ce0a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
@@ -45,6 +45,8 @@
 
   @Test
   public void testCustomCollectionD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addInnerClasses(CustomCollectionForwardingTest.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
index 4a92fd8..111ad12 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Collection;
@@ -56,6 +57,8 @@
           .assertSuccessWithOutput(EXPECTED_OUTPUT);
       return;
     }
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addInnerClasses(CustomCollectionInterfaceSuperTest.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
index 0097b3f..ac353f4 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.ArrayList;
 import java.util.List;
@@ -36,6 +37,8 @@
 
   @Test
   public void testCustomCollectionSuperCallsD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     D8TestRunResult d8TestRunResult =
         testForD8()
@@ -55,6 +58,8 @@
 
   @Test
   public void testCustomCollectionSuperCallsR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     R8TestRunResult r8TestRunResult =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index 2330854..eadaa9b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -52,6 +53,8 @@
 
   @Test
   public void testCustomCollectionD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     D8TestRunResult d8TestRunResult =
         testForD8()
@@ -84,6 +87,8 @@
 
   @Test
   public void testCustomCollectionR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     R8TestRunResult r8TestRunResult =
         testForR8(Backend.DEX)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
index ff07b24..92ec5ee 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -42,6 +43,8 @@
 
   @Test
   public void testD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addInnerClasses(DesugaredGenericSignatureTest.class)
@@ -61,6 +64,8 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .addInnerClasses(DesugaredGenericSignatureTest.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index e520016..e4b3869 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -7,6 +7,7 @@
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertTrue;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.L8Command;
@@ -167,6 +168,25 @@
     return new AbsentKeepRuleConsumer();
   }
 
+  public Path getDesugaredLibraryInCF(
+      TestParameters parameters, Consumer<InternalOptions> configurationForLibraryCompilation)
+      throws IOException, CompilationFailedException {
+    Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs.jar");
+    L8Command.Builder l8Builder =
+        L8Command.builder()
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+            .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+            .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+            .setMode(CompilationMode.DEBUG)
+            .addDesugaredLibraryConfiguration(
+                StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+            .setMinApiLevel(parameters.getApiLevel().getLevel())
+            .setOutput(desugaredLib, OutputMode.ClassFile);
+
+    ToolHelper.runL8(l8Builder.build(), configurationForLibraryCompilation);
+    return desugaredLib;
+  }
+
   public interface KeepRuleConsumer extends StringConsumer {
 
     String get();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLocalDateReflectedTypePassedToStaticType.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLocalDateReflectedTypePassedToStaticType.java
index c1c4ce3..ef92b08 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLocalDateReflectedTypePassedToStaticType.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLocalDateReflectedTypePassedToStaticType.java
@@ -26,7 +26,7 @@
   private final boolean shrinkDesugaredLibrary;
   private static final String EXPECTED = StringUtils.lines("1992");
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
@@ -40,6 +40,8 @@
 
   @Test
   public void testD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     D8TestRunResult runResult =
         testForD8()
@@ -64,6 +66,8 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     R8TestRunResult runResult =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredReflectedDesugaredTypePassedToStaticTypeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredReflectedDesugaredTypePassedToStaticTypeTest.java
index f56829c..ccf43f0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredReflectedDesugaredTypePassedToStaticTypeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredReflectedDesugaredTypePassedToStaticTypeTest.java
@@ -27,7 +27,7 @@
   private final boolean shrinkDesugaredLibrary;
   private static final String EXPECTED = StringUtils.lines("1992", "1992");
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
@@ -41,6 +41,8 @@
 
   @Test
   public void testD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     D8TestRunResult runResult =
         testForD8()
@@ -65,6 +67,8 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     R8TestRunResult runResult =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java
index d4f08fc..831d83a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java
@@ -50,6 +50,8 @@
 
   @Test
   public void testTwoFeatures() throws CompilationFailedException, IOException, ExecutionException {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     CompiledWithFeature compiledWithFeature = new CompiledWithFeature().invoke(this);
     Path basePath = compiledWithFeature.getBasePath();
     Path feature1Path = compiledWithFeature.getFeature1Path();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java
index 64f7a72..7c1f6a1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InvalidLibraryTest.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.errors.InvalidLibrarySuperclassDiagnostic;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -64,6 +65,11 @@
 
   @Test
   public void testProgramSupertype() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary
+            && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
+            && parameters.getDexRuntimeVersion() != Version.V5_1_1
+            && parameters.getDexRuntimeVersion() != Version.V6_0_1);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .setMinApi(parameters.getApiLevel())
@@ -84,6 +90,11 @@
 
   @Test
   public void testClasspathSupertype() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary
+            && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
+            && parameters.getDexRuntimeVersion() != Version.V5_1_1
+            && parameters.getDexRuntimeVersion() != Version.V6_0_1);
     Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index 558b5f1..bca838b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -13,12 +13,12 @@
 import com.android.tools.r8.L8Command;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.PrintUses;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.tracereferences.TraceReferences;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -32,6 +32,7 @@
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.android.tools.r8.utils.codeinspector.TryCatchSubject;
 import com.android.tools.r8.utils.codeinspector.TypeSubject;
+import com.google.common.base.Charsets;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
@@ -39,7 +40,6 @@
 import java.util.Set;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
-import kotlin.text.Charsets;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,7 +51,7 @@
 
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
-  private final boolean printUsesKeepRules;
+  private final boolean traceReferencesKeepRules;
   private static final String expectedOutput =
       StringUtils.lines(
           "Caught java.time.format.DateTimeParseException",
@@ -61,7 +61,7 @@
           "GMT",
           "Hello, world");
 
-  @Parameters(name = "{2}, shrinkDesugaredLibrary: {0}, printUsesKeepRules {1}")
+  @Parameters(name = "{2}, shrinkDesugaredLibrary: {0}, traceReferencesKeepRules {1}")
   public static List<Object[]> data() {
     return buildParameters(
         BooleanUtils.values(),
@@ -74,9 +74,9 @@
   }
 
   public JavaTimeTest(
-      boolean shrinkDesugaredLibrary, boolean printUsesKeepRules, TestParameters parameters) {
+      boolean shrinkDesugaredLibrary, boolean traceReferencesKeepRules, TestParameters parameters) {
     this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
-    this.printUsesKeepRules = printUsesKeepRules;
+    this.traceReferencesKeepRules = traceReferencesKeepRules;
     this.parameters = parameters;
   }
 
@@ -159,16 +159,16 @@
             }
           });
 
-  private String collectKeepRulesWithPrintUses(
+  private String collectKeepRulesWithTraceReferences(
       Path desugaredProgramClassFile, Path desugaredLibraryClassFile) throws Exception {
-    Path printUsesKeepRules = temp.newFile().toPath();
-    PrintUses.main(
-        "--keeprules",
-        ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
-        desugaredLibraryClassFile.toString(),
-        desugaredProgramClassFile.toString(),
-        printUsesKeepRules.toString());
-    return FileUtils.readTextFile(printUsesKeepRules, Charsets.UTF_8);
+    Path generatedKeepRules = temp.newFile().toPath();
+    TraceReferences.run(
+        "--format", "keep",
+        "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+        "--target", desugaredLibraryClassFile.toString(),
+        "--source", desugaredProgramClassFile.toString(),
+        "--output", generatedKeepRules.toString());
+    return FileUtils.readTextFile(generatedKeepRules, Charsets.UTF_8);
   }
 
   private String desugaredLibraryKeepRules(
@@ -178,9 +178,10 @@
     if (shrinkDesugaredLibrary) {
       desugaredLibraryKeepRules = keepRuleConsumer.get();
       if (desugaredLibraryKeepRules != null) {
-        if (printUsesKeepRules) {
+        if (traceReferencesKeepRules) {
           desugaredLibraryKeepRules =
-              collectKeepRulesWithPrintUses(programSupplier.get(), desugaredLibraryClassFile.get());
+              collectKeepRulesWithTraceReferences(
+                  programSupplier.get(), desugaredLibraryClassFile.get());
         }
       }
     }
@@ -189,7 +190,11 @@
 
   @Test
   public void testTimeD8Cf() throws Exception {
-    Assume.assumeTrue(shrinkDesugaredLibrary || !printUsesKeepRules);
+    Assume.assumeTrue(shrinkDesugaredLibrary || !traceReferencesKeepRules);
+    expectThrowsWithHorizontalClassMergingIf(
+        parameters.isDexRuntime()
+            && traceReferencesKeepRules
+            && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
 
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     // Use D8 to desugar with Java classfile output.
@@ -203,7 +208,7 @@
             .writeToZip();
 
     String desugaredLibraryKeepRules;
-    if (shrinkDesugaredLibrary && !printUsesKeepRules && keepRuleConsumer.get() != null) {
+    if (shrinkDesugaredLibrary && !traceReferencesKeepRules && keepRuleConsumer.get() != null) {
       // Collection keep rules is only implemented in the DEX writer.
       assertEquals(0, keepRuleConsumer.get().length());
       desugaredLibraryKeepRules = "-keep class * { *; }";
@@ -239,7 +244,9 @@
   @Test
   public void testTimeD8() throws Exception {
     Assume.assumeTrue(parameters.getRuntime().isDex());
-    Assume.assumeTrue(shrinkDesugaredLibrary || !printUsesKeepRules);
+    Assume.assumeTrue(shrinkDesugaredLibrary || !traceReferencesKeepRules);
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
 
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     TestCompileResult<?, ?> result =
@@ -261,8 +268,10 @@
 
   @Test
   public void testTimeR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     Assume.assumeTrue(parameters.getRuntime().isDex());
-    Assume.assumeTrue(shrinkDesugaredLibrary || !printUsesKeepRules);
+    Assume.assumeTrue(shrinkDesugaredLibrary || !traceReferencesKeepRules);
 
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     TestCompileResult<?, ?> result =
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
index 0eab37e..c66ff1d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -36,6 +37,8 @@
 
   @Test
   public void testD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
@@ -64,6 +67,8 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(Backend.DEX)
         .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
index 6e300ba..d69b28c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import dalvik.system.PathClassLoader;
 import java.sql.SQLDataException;
@@ -49,6 +50,8 @@
 
   @Test
   public void testCustomCollectionD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdOut =
         testForD8()
@@ -69,6 +72,8 @@
 
   @Test
   public void testCustomCollectionR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdOut =
         testForR8(Backend.DEX)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index 69f366f..b0c3645 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -54,6 +54,8 @@
 
   @Test
   public void testProgramD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     ArrayList<Path> coreLambdaStubs = new ArrayList<>();
     coreLambdaStubs.add(ToolHelper.getCoreLambdaStubs());
     for (Boolean coreLambdaStubsActive : BooleanUtils.values()) {
@@ -111,6 +113,8 @@
 
   @Test
   public void testProgramR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     Assume.assumeTrue(
         "TODO(b/139451198): Make the test run with new SDK.",
         parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel());
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
index c2d88b0..7c65b9e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.time.Instant;
 import java.time.ZonedDateTime;
@@ -37,6 +39,11 @@
 
   @Test
   public void testRetargetOverrideD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary
+            && parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K)
+            && parameters.getDexRuntimeVersion() != Version.V5_1_1
+            && parameters.getDexRuntimeVersion() != Version.V6_0_1);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdout =
         testForD8()
@@ -57,6 +64,11 @@
 
   @Test
   public void testRetargetOverrideR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary
+            && parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K)
+            && parameters.getDexRuntimeVersion() != Version.V5_1_1
+            && parameters.getDexRuntimeVersion() != Version.V6_0_1);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdout =
         testForR8(Backend.DEX)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java
index 84917f1..5d50648 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/StaticInterfaceMethodTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.time.chrono.Chronology;
@@ -44,6 +45,8 @@
           .assertSuccessWithOutput(EXPECTED_OUTPUT);
       return;
     }
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addInnerClasses(StaticInterfaceMethodTest.class)
@@ -64,6 +67,8 @@
     // Desugared library tests do not make sense in the Cf to Cf, and the JVM is already tested
     // in the D8 test. Just return.
     Assume.assumeFalse(parameters.isCfRuntime());
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .addKeepMainRule(Executor.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java
index f9d1828..9e23d5d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalClassTest.java
@@ -53,6 +53,8 @@
 
   @Test
   public void testFinalMethod() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .setMinApi(AndroidApiLevel.B)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AllTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AllTimeConversionTest.java
index 5a03f73..2726e3e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AllTimeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/AllTimeConversionTest.java
@@ -69,6 +69,8 @@
 
   @Test
   public void testRewrittenAPICallsD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .setMinApi(parameters.getApiLevel())
@@ -90,6 +92,8 @@
 
   @Test
   public void testRewrittenAPICallsR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/BasicLongDoubleConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/BasicLongDoubleConversionTest.java
index 4ac3ccc..dfa62f1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/BasicLongDoubleConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/BasicLongDoubleConversionTest.java
@@ -55,6 +55,8 @@
 
   @Test
   public void testRewrittenAPICallsD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.K));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .setMinApi(parameters.getApiLevel())
@@ -74,6 +76,8 @@
 
   @Test
   public void testRewrittenAPICallsR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.K));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
index 382f55d..8583e29 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
@@ -50,6 +50,8 @@
 
   @Test
   public void testClockD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .setMinApi(parameters.getApiLevel())
@@ -70,6 +72,8 @@
 
   @Test
   public void testClockR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .addKeepMainRule(Executor.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
index 14f3ded..572f5b3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
@@ -68,6 +68,7 @@
 
   @Test
   public void testNoInterfaceMethodsD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(shrinkDesugaredLibrary);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
index 7fe00d5..9bce171 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
@@ -66,6 +66,7 @@
 
   @Test
   public void testStatsD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(shrinkDesugaredLibrary && shrinkDesugaredLibrary);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .setMinApi(parameters.getApiLevel())
@@ -85,6 +86,7 @@
 
   @Test
   public void testStatsR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(shrinkDesugaredLibrary && shrinkDesugaredLibrary);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java
index 4dbcccb..d983dfa 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/TryCatchTimeConversionTest.java
@@ -54,6 +54,8 @@
 
   @Test
   public void testBaselineD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .setMinApi(parameters.getApiLevel())
@@ -73,6 +75,8 @@
 
   @Test
   public void testBaselineR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
@@ -93,6 +97,8 @@
 
   @Test
   public void testTryCatchD8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .setMinApi(parameters.getApiLevel())
@@ -112,6 +118,8 @@
 
   @Test
   public void testTryCatchR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
index 2801eb9..8e96ae1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
@@ -72,6 +73,8 @@
 
   @Test
   public void testD8AtomicReference() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String verbosity = "2";
     testForD8()
@@ -95,6 +98,8 @@
 
   @Test
   public void testD8AtomicUpdaters() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String verbosity = "2";
     testForD8()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
index 7d0fc6b..19b1b1e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
@@ -98,6 +98,8 @@
 
   @Test
   public void testD8Concurrent() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     // TODO(b/134732760): Support Java 9+ libraries.
     // We skip the ConcurrentRemoveIf test because of the  non desugared class CompletableFuture.
     Assume.assumeFalse("b/144975341",
@@ -157,6 +159,8 @@
 
   @Test
   public void testD8ConcurrentHash() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     Assume.assumeFalse("b/144975341",
         parameters.getRuntime().asDex().getVm().getVersion() == Version.V10_0_0);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
index 9d9b5cb..3268a35 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
@@ -181,6 +181,8 @@
 
   @Test
   public void testStream() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     Assume.assumeFalse(
         "getAllFilesWithSuffixInDirectory() seems to find different files on Windows",
         ToolHelper.isWindows());
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
index 6288598..4ad9faa 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
@@ -133,6 +133,8 @@
 
   @Test
   public void testTime() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String verbosity = "2";
     D8TestCompileResult compileResult =
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
index 9004df6..61e7c03 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -131,6 +132,10 @@
   @Test
   public void testTimeR8() throws Exception {
     boolean desugarLibrary = parameters.isDexRuntime() && requiresAnyCoreLibDesugaring(parameters);
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary
+            && parameters.isDexRuntime()
+            && parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.N));
     final R8FullTestBuilder testBuilder =
         testForR8(parameters.getBackend())
             .addProgramFiles(compiledJars.get(targetVersion))
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/FieldAccessTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/FieldAccessTest.java
index 5211d3d..6c48313 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/FieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/FieldAccessTest.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.time.ZoneId;
@@ -34,6 +35,8 @@
 
   @Test
   public void testField() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addProgramClasses(Executor.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/InheritanceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/InheritanceTest.java
index a6061c8..da15390 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/InheritanceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/InheritanceTest.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.time.Clock;
@@ -36,6 +37,8 @@
 
   @Test
   public void testInheritance() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addProgramClasses(Impl.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/KeepRuleShrinkTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/KeepRuleShrinkTest.java
index 2a673bf..2295e35 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/KeepRuleShrinkTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/shrinkingtests/KeepRuleShrinkTest.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.List;
 import java.util.Map;
@@ -37,6 +38,8 @@
 
   @Test
   public void testMapProblem() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     D8TestRunResult d8TestRunResult =
         testForD8()
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
index 5a788bf..cbd5710 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -57,8 +57,11 @@
 
   @BeforeClass
   public static void beforeAll() throws Exception {
-    r8Lib11NoDesugar = compileR8(false);
-    r8Lib11Desugar = compileR8(true);
+    assertThrowsWithHorizontalClassMerging(
+        () -> {
+          r8Lib11NoDesugar = compileR8(false);
+          r8Lib11Desugar = compileR8(true);
+        });
   }
 
   private static Path compileR8(boolean desugar) throws Exception {
@@ -94,6 +97,7 @@
 
   @Test
   public void testHello() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     Assume.assumeTrue(!ToolHelper.isWindows());
     Path prevGeneratedJar = null;
     String prevRunResult = null;
@@ -127,6 +131,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     Assume.assumeTrue(!ToolHelper.isWindows());
     Assume.assumeTrue(parameters.isCfRuntime());
     Assume.assumeTrue(CfVm.JDK11 == parameters.getRuntime().asCf().getVm());
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
index 762916e..e1d892d 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
@@ -47,6 +47,7 @@
 
   @Test
   public void testR8CompiledWithR8() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java
index ee31562..e45ef92 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java
@@ -60,6 +60,7 @@
 
   @Test
   public void testClassMergeAcrossNestAndNonNest() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     // Potentially merge classes from a nest with non nest classes.
     testClassMergeAcrossNest(
         new String[] {
@@ -95,7 +96,7 @@
                   options.enableClassInlining = false;
                   options.enableNestReduction = false;
                 })
-            .enableInliningAnnotations("nesthostexample")
+            .enableInliningAnnotations()
             .addProgramFiles(bothNestsAndOutsideClassToCompile)
             .compile()
             .inspect(NestAttributesUpdateTest::assertNestAttributesCorrect);
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
index 4fcfea3..373f6fc 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
@@ -59,7 +59,7 @@
               options.enableClassInlining = false;
               options.enableVerticalClassMerging = false;
             })
-        .enableInliningAnnotations("nesthostexample")
+        .enableInliningAnnotations()
         .addProgramFiles(toCompile)
         .compile()
         .inspect(this::assertMethodsInlined)
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index c9dd274..9431067 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -49,6 +49,7 @@
 
   @Test
   public void testInliningFromFeature() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     // Static merging is based on sorting order, we assert that we merged to the feature.
     ThrowingConsumer<R8TestCompileResult, Exception> ensureMergingToFeature =
         r8TestCompileResult -> {
diff --git a/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java b/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java
index 9251ab8..da5dc5c 100644
--- a/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java
@@ -43,6 +43,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(InitializedClassesInInstanceMethodsTest.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/DebugLocalStartOutsideRangeTest.java b/src/test/java/com/android/tools/r8/ir/DebugLocalStartOutsideRangeTest.java
new file mode 100644
index 0000000..ac6d2dd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/DebugLocalStartOutsideRangeTest.java
@@ -0,0 +1,493 @@
+// 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.ir;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.ir.DebugLocalStartOutsideRangeTest.PrintHelper$PrintUriAdapter$1Dump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class DebugLocalStartOutsideRangeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public DebugLocalStartOutsideRangeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws CompilationFailedException {
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertNoWarningsMatch(
+                  diagnosticMessage(containsString("Could not find phi type for register")));
+            });
+  }
+
+  public static class PrintHelper$PrintUriAdapter$1Dump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      FieldVisitor fieldVisitor;
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_7,
+          ACC_SUPER,
+          "androidx/print/PrintHelper$PrintUriAdapter$1",
+          "Landroid/os/AsyncTask<Landroid/net/Uri;Ljava/lang/Boolean;Landroid/graphics/Bitmap;>;",
+          "android/os/AsyncTask",
+          null);
+
+      classWriter.visitSource("PrintHelper.java", null);
+
+      classWriter.visitOuterClass(
+          "androidx/print/PrintHelper$PrintUriAdapter",
+          "onLayout",
+          "(Landroid/print/PrintAttributes;Landroid/print/PrintAttributes;Landroid/os/CancellationSignal;Landroid/print/PrintDocumentAdapter$LayoutResultCallback;Landroid/os/Bundle;)V");
+
+      classWriter.visitInnerClass(
+          "androidx/print/PrintHelper$PrintUriAdapter",
+          "androidx/print/PrintHelper",
+          "PrintUriAdapter",
+          ACC_PRIVATE);
+
+      classWriter.visitInnerClass("androidx/print/PrintHelper$PrintUriAdapter$1", null, null, 0);
+
+      classWriter.visitInnerClass(
+          "android/print/PrintAttributes$MediaSize",
+          "android/print/PrintAttributes",
+          "MediaSize",
+          ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
+
+      classWriter.visitInnerClass(
+          "android/print/PrintDocumentInfo$Builder",
+          "android/print/PrintDocumentInfo",
+          "Builder",
+          ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
+
+      {
+        fieldVisitor =
+            classWriter.visitField(
+                ACC_FINAL | ACC_SYNTHETIC,
+                "val$cancellationSignal",
+                "Landroid/os/CancellationSignal;",
+                null,
+                null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor =
+            classWriter.visitField(
+                ACC_FINAL | ACC_SYNTHETIC,
+                "val$newPrintAttributes",
+                "Landroid/print/PrintAttributes;",
+                null,
+                null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor =
+            classWriter.visitField(
+                ACC_FINAL | ACC_SYNTHETIC,
+                "val$oldPrintAttributes",
+                "Landroid/print/PrintAttributes;",
+                null,
+                null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor =
+            classWriter.visitField(
+                ACC_FINAL | ACC_SYNTHETIC,
+                "val$layoutResultCallback",
+                "Landroid/print/PrintDocumentAdapter$LayoutResultCallback;",
+                null,
+                null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor =
+            classWriter.visitField(
+                ACC_FINAL | ACC_SYNTHETIC,
+                "this$1",
+                "Landroidx/print/PrintHelper$PrintUriAdapter;",
+                null,
+                null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PROTECTED, "onPostExecute", "(Landroid/graphics/Bitmap;)V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        Label label1 = new Label();
+        Label label2 = new Label();
+        methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
+        Label label3 = new Label();
+        methodVisitor.visitTryCatchBlock(label2, label3, label2, null);
+        Label label4 = new Label();
+        methodVisitor.visitLabel(label4);
+        methodVisitor.visitLineNumber(450, label4);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL, "android/os/AsyncTask", "onPostExecute", "(Ljava/lang/Object;)V", false);
+        Label label5 = new Label();
+        methodVisitor.visitLabel(label5);
+        methodVisitor.visitLineNumber(454, label5);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        Label label6 = new Label();
+        methodVisitor.visitJumpInsn(IFNULL, label6);
+        methodVisitor.visitFieldInsn(
+            GETSTATIC, "androidx/print/PrintHelper", "PRINT_ACTIVITY_RESPECTS_ORIENTATION", "Z");
+        Label label7 = new Label();
+        methodVisitor.visitJumpInsn(IFEQ, label7);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter$1",
+            "this$1",
+            "Landroidx/print/PrintHelper$PrintUriAdapter;");
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter",
+            "this$0",
+            "Landroidx/print/PrintHelper;");
+        methodVisitor.visitFieldInsn(GETFIELD, "androidx/print/PrintHelper", "mOrientation", "I");
+        methodVisitor.visitJumpInsn(IFNE, label6);
+        methodVisitor.visitLabel(label7);
+        methodVisitor.visitLineNumber(458, label7);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitVarInsn(ASTORE, 3);
+        methodVisitor.visitInsn(MONITORENTER);
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(459, label0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter$1",
+            "this$1",
+            "Landroidx/print/PrintHelper$PrintUriAdapter;");
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter",
+            "mAttributes",
+            "Landroid/print/PrintAttributes;");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "android/print/PrintAttributes",
+            "getMediaSize",
+            "()Landroid/print/PrintAttributes$MediaSize;",
+            false);
+        methodVisitor.visitVarInsn(ASTORE, 2);
+        Label label8 = new Label();
+        methodVisitor.visitLabel(label8);
+        methodVisitor.visitLineNumber(460, label8);
+        methodVisitor.visitVarInsn(ALOAD, 3);
+        methodVisitor.visitInsn(MONITOREXIT);
+        methodVisitor.visitLabel(label1);
+        Label label9 = new Label();
+        methodVisitor.visitJumpInsn(GOTO, label9);
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            4,
+            new Object[] {
+              "androidx/print/PrintHelper$PrintUriAdapter$1",
+              "android/graphics/Bitmap",
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            1,
+            new Object[] {"java/lang/Throwable"});
+        methodVisitor.visitVarInsn(ASTORE, 4);
+        methodVisitor.visitVarInsn(ALOAD, 3);
+        methodVisitor.visitInsn(MONITOREXIT);
+        methodVisitor.visitLabel(label3);
+        methodVisitor.visitVarInsn(ALOAD, 4);
+        methodVisitor.visitInsn(ATHROW);
+        methodVisitor.visitLabel(label9);
+        methodVisitor.visitLineNumber(462, label9);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            3,
+            new Object[] {
+              "androidx/print/PrintHelper$PrintUriAdapter$1",
+              "android/graphics/Bitmap",
+              "android/print/PrintAttributes$MediaSize"
+            },
+            0,
+            new Object[] {});
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitJumpInsn(IFNULL, label6);
+        Label label10 = new Label();
+        methodVisitor.visitLabel(label10);
+        methodVisitor.visitLineNumber(463, label10);
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "android/print/PrintAttributes$MediaSize", "isPortrait", "()Z", false);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "androidx/print/PrintHelper",
+            "isPortrait",
+            "(Landroid/graphics/Bitmap;)Z",
+            false);
+        methodVisitor.visitJumpInsn(IF_ICMPEQ, label6);
+        Label label11 = new Label();
+        methodVisitor.visitLabel(label11);
+        methodVisitor.visitLineNumber(464, label11);
+        methodVisitor.visitTypeInsn(NEW, "android/graphics/Matrix");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL, "android/graphics/Matrix", "<init>", "()V", false);
+        methodVisitor.visitVarInsn(ASTORE, 3);
+        Label label12 = new Label();
+        methodVisitor.visitLabel(label12);
+        methodVisitor.visitLineNumber(466, label12);
+        methodVisitor.visitVarInsn(ALOAD, 3);
+        methodVisitor.visitLdcInsn(new Float("90.0"));
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "android/graphics/Matrix", "postRotate", "(F)Z", false);
+        methodVisitor.visitInsn(POP);
+        Label label13 = new Label();
+        methodVisitor.visitLabel(label13);
+        methodVisitor.visitLineNumber(467, label13);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        Label label14 = new Label();
+        methodVisitor.visitLabel(label14);
+        methodVisitor.visitLineNumber(468, label14);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "android/graphics/Bitmap", "getWidth", "()I", false);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "android/graphics/Bitmap", "getHeight", "()I", false);
+        methodVisitor.visitVarInsn(ALOAD, 3);
+        methodVisitor.visitInsn(ICONST_1);
+        Label label15 = new Label();
+        methodVisitor.visitLabel(label15);
+        methodVisitor.visitLineNumber(467, label15);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "android/graphics/Bitmap",
+            "createBitmap",
+            "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)Landroid/graphics/Bitmap;",
+            false);
+        methodVisitor.visitVarInsn(ASTORE, 1);
+        methodVisitor.visitLabel(label6);
+        methodVisitor.visitLineNumber(474, label6);
+        methodVisitor.visitFrame(Opcodes.F_CHOP, 1, null, 0, null);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter$1",
+            "this$1",
+            "Landroidx/print/PrintHelper$PrintUriAdapter;");
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter",
+            "mBitmap",
+            "Landroid/graphics/Bitmap;");
+        Label label16 = new Label();
+        methodVisitor.visitLabel(label16);
+        methodVisitor.visitLineNumber(475, label16);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        Label label17 = new Label();
+        methodVisitor.visitJumpInsn(IFNULL, label17);
+        Label label18 = new Label();
+        methodVisitor.visitLabel(label18);
+        methodVisitor.visitLineNumber(476, label18);
+        methodVisitor.visitTypeInsn(NEW, "android/print/PrintDocumentInfo$Builder");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter$1",
+            "this$1",
+            "Landroidx/print/PrintHelper$PrintUriAdapter;");
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter",
+            "mJobName",
+            "Ljava/lang/String;");
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "android/print/PrintDocumentInfo$Builder",
+            "<init>",
+            "(Ljava/lang/String;)V",
+            false);
+        methodVisitor.visitInsn(ICONST_1);
+        Label label19 = new Label();
+        methodVisitor.visitLabel(label19);
+        methodVisitor.visitLineNumber(477, label19);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "android/print/PrintDocumentInfo$Builder",
+            "setContentType",
+            "(I)Landroid/print/PrintDocumentInfo$Builder;",
+            false);
+        methodVisitor.visitInsn(ICONST_1);
+        Label label20 = new Label();
+        methodVisitor.visitLabel(label20);
+        methodVisitor.visitLineNumber(478, label20);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "android/print/PrintDocumentInfo$Builder",
+            "setPageCount",
+            "(I)Landroid/print/PrintDocumentInfo$Builder;",
+            false);
+        Label label21 = new Label();
+        methodVisitor.visitLabel(label21);
+        methodVisitor.visitLineNumber(479, label21);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "android/print/PrintDocumentInfo$Builder",
+            "build",
+            "()Landroid/print/PrintDocumentInfo;",
+            false);
+        methodVisitor.visitVarInsn(ASTORE, 2);
+        Label label22 = new Label();
+        methodVisitor.visitLabel(label22);
+        methodVisitor.visitLineNumber(481, label22);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter$1",
+            "val$newPrintAttributes",
+            "Landroid/print/PrintAttributes;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter$1",
+            "val$oldPrintAttributes",
+            "Landroid/print/PrintAttributes;");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "android/print/PrintAttributes",
+            "equals",
+            "(Ljava/lang/Object;)Z",
+            false);
+        Label label23 = new Label();
+        methodVisitor.visitJumpInsn(IFNE, label23);
+        methodVisitor.visitInsn(ICONST_1);
+        Label label24 = new Label();
+        methodVisitor.visitJumpInsn(GOTO, label24);
+        methodVisitor.visitLabel(label23);
+        methodVisitor.visitFrame(
+            Opcodes.F_APPEND, 1, new Object[] {"android/print/PrintDocumentInfo"}, 0, null);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitLabel(label24);
+        methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
+        methodVisitor.visitVarInsn(ISTORE, 3);
+        Label label25 = new Label();
+        methodVisitor.visitLabel(label25);
+        methodVisitor.visitLineNumber(483, label25);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter$1",
+            "val$layoutResultCallback",
+            "Landroid/print/PrintDocumentAdapter$LayoutResultCallback;");
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitVarInsn(ILOAD, 3);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "android/print/PrintDocumentAdapter$LayoutResultCallback",
+            "onLayoutFinished",
+            "(Landroid/print/PrintDocumentInfo;Z)V",
+            false);
+        Label label26 = new Label();
+        methodVisitor.visitLabel(label26);
+        methodVisitor.visitLineNumber(485, label26);
+        Label label27 = new Label();
+        methodVisitor.visitJumpInsn(GOTO, label27);
+        methodVisitor.visitLabel(label17);
+        methodVisitor.visitLineNumber(486, label17);
+        methodVisitor.visitFrame(Opcodes.F_CHOP, 1, null, 0, null);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter$1",
+            "val$layoutResultCallback",
+            "Landroid/print/PrintDocumentAdapter$LayoutResultCallback;");
+        methodVisitor.visitInsn(ACONST_NULL);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "android/print/PrintDocumentAdapter$LayoutResultCallback",
+            "onLayoutFailed",
+            "(Ljava/lang/CharSequence;)V",
+            false);
+        methodVisitor.visitLabel(label27);
+        methodVisitor.visitLineNumber(488, label27);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter$1",
+            "this$1",
+            "Landroidx/print/PrintHelper$PrintUriAdapter;");
+        methodVisitor.visitInsn(ACONST_NULL);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "androidx/print/PrintHelper$PrintUriAdapter",
+            "mLoadBitmap",
+            "Landroid/os/AsyncTask;");
+        Label label28 = new Label();
+        methodVisitor.visitLabel(label28);
+        methodVisitor.visitLineNumber(489, label28);
+        methodVisitor.visitInsn(RETURN);
+        Label label29 = new Label();
+        methodVisitor.visitLabel(label29);
+        methodVisitor.visitLocalVariable(
+            "rotation", "Landroid/graphics/Matrix;", null, label12, label6, 3);
+        methodVisitor.visitLocalVariable(
+            "mediaSize", "Landroid/print/PrintAttributes$MediaSize;", null, label8, label6, 2);
+        methodVisitor.visitLocalVariable(
+            "info", "Landroid/print/PrintDocumentInfo;", null, label22, label26, 2);
+        methodVisitor.visitLocalVariable("changed", "Z", null, label25, label26, 3);
+        methodVisitor.visitLocalVariable(
+            "this", "Landroidx/print/PrintHelper$PrintUriAdapter$1;", null, label4, label29, 0);
+        methodVisitor.visitLocalVariable(
+            "bitmap", "Landroid/graphics/Bitmap;", null, label4, label29, 1);
+        methodVisitor.visitMaxs(7, 5);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/DebugLocalStartRangeInLinearBlockWithFrameTest.java b/src/test/java/com/android/tools/r8/ir/DebugLocalStartRangeInLinearBlockWithFrameTest.java
new file mode 100644
index 0000000..0b3e53b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/DebugLocalStartRangeInLinearBlockWithFrameTest.java
@@ -0,0 +1,400 @@
+// 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.ir;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+@RunWith(Parameterized.class)
+public class DebugLocalStartRangeInLinearBlockWithFrameTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public DebugLocalStartRangeInLinearBlockWithFrameTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(AbstractAjaxCallbackDump.dump())
+        .setMinApi(parameters.getApiLevel())
+        .compile();
+  }
+
+  public static class AbstractAjaxCallbackDump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      FieldVisitor fieldVisitor;
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_6,
+          ACC_PUBLIC | ACC_SUPER | ACC_ABSTRACT,
+          "com/androidquery/callback/AbstractAjaxCallback",
+          "<T:Ljava/lang/Object;K:Ljava/lang/Object;>Ljava/lang/Object;Ljava/lang/Runnable;",
+          "java/lang/Object",
+          new String[] {"java/lang/Runnable"});
+
+      classWriter.visitSource("AbstractAjaxCallback.java", null);
+
+      classWriter.visitInnerClass(
+          "android/os/Build$VERSION", "android/os/Build", "VERSION", ACC_PUBLIC | ACC_STATIC);
+
+      classWriter.visitInnerClass(
+          "com/androidquery/callback/AbstractAjaxCallback$1", null, null, 0);
+
+      classWriter.visitInnerClass(
+          "java/net/Proxy$Type",
+          "java/net/Proxy",
+          "Type",
+          ACC_PUBLIC | ACC_FINAL | ACC_STATIC | ACC_ENUM);
+
+      classWriter.visitInnerClass(
+          "java/util/Map$Entry",
+          "java/util/Map",
+          "Entry",
+          ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
+
+      {
+        fieldVisitor =
+            classWriter.visitField(
+                ACC_PRIVATE, "type", "Ljava/lang/Class;", "Ljava/lang/Class<TT;>;", null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor =
+            classWriter.visitField(ACC_PRIVATE, "handler", "Ljava/lang/Object;", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor =
+            classWriter.visitField(ACC_PRIVATE, "callback", "Ljava/lang/String;", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor = classWriter.visitField(ACC_PRIVATE, "url", "Ljava/lang/String;", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor =
+            classWriter.visitField(ACC_PROTECTED, "result", "Ljava/lang/Object;", "TT;", null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor =
+            classWriter.visitField(
+                ACC_PROTECTED, "status", "Lcom/androidquery/callback/AjaxStatus;", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor = classWriter.visitField(ACC_PRIVATE, "completed", "Z", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor = classWriter.visitField(ACC_PRIVATE, "blocked", "Z", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(0, "callback", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        Label label1 = new Label();
+        Label label2 = new Label();
+        methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/Exception");
+        Label label3 = new Label();
+        methodVisitor.visitLabel(label3);
+        methodVisitor.visitLineNumber(568, label3);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "showProgress",
+            "(Z)V",
+            false);
+        Label label4 = new Label();
+        methodVisitor.visitLabel(label4);
+        methodVisitor.visitLineNumber(570, label4);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitInsn(ICONST_1);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD, "com/androidquery/callback/AbstractAjaxCallback", "completed", "Z");
+        Label label5 = new Label();
+        methodVisitor.visitLabel(label5);
+        methodVisitor.visitLineNumber(572, label5);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "isActive",
+            "()Z",
+            false);
+        Label label6 = new Label();
+        methodVisitor.visitJumpInsn(IFEQ, label6);
+        Label label7 = new Label();
+        methodVisitor.visitLabel(label7);
+        methodVisitor.visitLineNumber(574, label7);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "callback",
+            "Ljava/lang/String;");
+        methodVisitor.visitJumpInsn(IFNULL, label0);
+        Label label8 = new Label();
+        methodVisitor.visitLabel(label8);
+        methodVisitor.visitLineNumber(575, label8);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "getHandler",
+            "()Ljava/lang/Object;",
+            false);
+        methodVisitor.visitVarInsn(ASTORE, 1);
+        Label label9 = new Label();
+        methodVisitor.visitLabel(label9);
+        methodVisitor.visitLineNumber(576, label9);
+        methodVisitor.visitInsn(ICONST_3);
+        methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Class");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitLdcInsn(Type.getType("Ljava/lang/String;"));
+        methodVisitor.visitInsn(AASTORE);
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitInsn(ICONST_1);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "type",
+            "Ljava/lang/Class;");
+        methodVisitor.visitInsn(AASTORE);
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitInsn(ICONST_2);
+        methodVisitor.visitLdcInsn(Type.getType("Lcom/androidquery/callback/AjaxStatus;"));
+        methodVisitor.visitInsn(AASTORE);
+        methodVisitor.visitVarInsn(ASTORE, 2);
+        Label label10 = new Label();
+        methodVisitor.visitLabel(label10);
+        methodVisitor.visitLineNumber(577, label10);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "callback",
+            "Ljava/lang/String;");
+        methodVisitor.visitInsn(ICONST_1);
+        methodVisitor.visitInsn(ICONST_1);
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitFieldInsn(
+            GETSTATIC,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "DEFAULT_SIG",
+            "[Ljava/lang/Class;");
+        methodVisitor.visitInsn(ICONST_3);
+        methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Object");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "url",
+            "Ljava/lang/String;");
+        methodVisitor.visitInsn(AASTORE);
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitInsn(ICONST_1);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "result",
+            "Ljava/lang/Object;");
+        methodVisitor.visitInsn(AASTORE);
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitInsn(ICONST_2);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "status",
+            "Lcom/androidquery/callback/AjaxStatus;");
+        methodVisitor.visitInsn(AASTORE);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "com/androidquery/util/AQUtility",
+            "invokeHandler",
+            "(Ljava/lang/Object;Ljava/lang/String;ZZ[Ljava/lang/Class;[Ljava/lang/Class;[Ljava/lang/Object;)Ljava/lang/Object;",
+            false);
+        methodVisitor.visitInsn(POP);
+        Label label11 = new Label();
+        methodVisitor.visitLabel(label11);
+        methodVisitor.visitLineNumber(578, label11);
+        Label label12 = new Label();
+        methodVisitor.visitJumpInsn(GOTO, label12);
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(580, label0);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "url",
+            "Ljava/lang/String;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "result",
+            "Ljava/lang/Object;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "status",
+            "Lcom/androidquery/callback/AjaxStatus;");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "callback",
+            "(Ljava/lang/String;Ljava/lang/Object;Lcom/androidquery/callback/AjaxStatus;)V",
+            false);
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(581, label1);
+        methodVisitor.visitJumpInsn(GOTO, label12);
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Exception"});
+        methodVisitor.visitVarInsn(ASTORE, 1);
+        Label label13 = new Label();
+        methodVisitor.visitLabel(label13);
+        methodVisitor.visitLineNumber(582, label13);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "com/androidquery/util/AQUtility",
+            "report",
+            "(Ljava/lang/Throwable;)V",
+            false);
+        Label label14 = new Label();
+        methodVisitor.visitLabel(label14);
+        methodVisitor.visitLineNumber(586, label14);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitJumpInsn(GOTO, label12);
+        methodVisitor.visitLabel(label6);
+        methodVisitor.visitLineNumber(587, label6);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "url",
+            "Ljava/lang/String;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "result",
+            "Ljava/lang/Object;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "status",
+            "Lcom/androidquery/callback/AjaxStatus;");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "skip",
+            "(Ljava/lang/String;Ljava/lang/Object;Lcom/androidquery/callback/AjaxStatus;)V",
+            false);
+        methodVisitor.visitLabel(label12);
+        methodVisitor.visitLineNumber(591, label12);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "filePut",
+            "()V",
+            false);
+        Label label15 = new Label();
+        methodVisitor.visitLabel(label15);
+        methodVisitor.visitLineNumber(593, label15);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD, "com/androidquery/callback/AbstractAjaxCallback", "blocked", "Z");
+        Label label16 = new Label();
+        methodVisitor.visitJumpInsn(IFNE, label16);
+        Label label17 = new Label();
+        methodVisitor.visitLabel(label17);
+        methodVisitor.visitLineNumber(594, label17);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/androidquery/callback/AbstractAjaxCallback",
+            "status",
+            "Lcom/androidquery/callback/AjaxStatus;");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "com/androidquery/callback/AjaxStatus", "close", "()V", false);
+        methodVisitor.visitLabel(label16);
+        methodVisitor.visitLineNumber(597, label16);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL, "com/androidquery/callback/AbstractAjaxCallback", "wake", "()V", false);
+        Label label18 = new Label();
+        methodVisitor.visitLabel(label18);
+        methodVisitor.visitLineNumber(598, label18);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC, "com/androidquery/util/AQUtility", "debugNotify", "()V", false);
+        Label label19 = new Label();
+        methodVisitor.visitLabel(label19);
+        methodVisitor.visitLineNumber(599, label19);
+        methodVisitor.visitInsn(RETURN);
+        Label label20 = new Label();
+        methodVisitor.visitLabel(label20);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/androidquery/callback/AbstractAjaxCallback;",
+            "Lcom/androidquery/callback/AbstractAjaxCallback<TT;TK;>;",
+            label3,
+            label20,
+            0);
+        methodVisitor.visitLocalVariable("handler", "Ljava/lang/Object;", null, label9, label11, 1);
+        methodVisitor.visitLocalVariable(
+            "AJAX_SIG", "[Ljava/lang/Class;", null, label10, label11, 2);
+        methodVisitor.visitLocalVariable("e", "Ljava/lang/Exception;", null, label13, label14, 1);
+        methodVisitor.visitMaxs(10, 3);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/InvokeMultiNewArraySideEffectTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/InvokeMultiNewArraySideEffectTest.java
index 487d60a..11268e8 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/InvokeMultiNewArraySideEffectTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/InvokeMultiNewArraySideEffectTest.java
@@ -34,6 +34,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(parameters.isCfRuntime());
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeMultiNewArraySideEffectTest.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
index dbbf99a..2eb4a58 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
@@ -41,6 +41,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(PutObjectWithFinalizeTest.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
index 964848a..4429dc9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
@@ -171,6 +171,7 @@
 
   @Test
   public void testNonNullParamAfterInvokeVirtual() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     Class<?> mainClass = NonNullParamAfterInvokeVirtualMain.class;
     CodeInspector inspector =
         buildAndRun(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 807b061..143f206 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -131,32 +131,35 @@
 
   @Before
   public void generateR8Version() throws Exception {
-    outputDir = temp.newFolder().toPath();
-    Path mapFile = outputDir.resolve(DEFAULT_MAP_FILENAME);
-    generateR8Version(outputDir, mapFile, true);
-    String output;
-    if (parameters.isDexRuntime()) {
-      output =
-          ToolHelper.runArtNoVerificationErrors(
-              Collections.singletonList(outputDir.resolve(DEFAULT_DEX_FILENAME).toString()),
-              "inlining.Inlining",
-              builder -> {},
-              parameters.getRuntime().asDex().getVm());
-    } else {
-      assert parameters.isCfRuntime();
-      output =
-          ToolHelper.runJava(
-                  parameters.getRuntime().asCf(),
-                  Collections.singletonList("-noverify"),
-                  Collections.singletonList(outputDir),
-                  "inlining.Inlining")
-              .stdout;
-    }
+    assertThrowsWithHorizontalClassMerging(
+        () -> {
+          outputDir = temp.newFolder().toPath();
+          Path mapFile = outputDir.resolve(DEFAULT_MAP_FILENAME);
+          generateR8Version(outputDir, mapFile, true);
+          String output;
+          if (parameters.isDexRuntime()) {
+            output =
+                ToolHelper.runArtNoVerificationErrors(
+                    Collections.singletonList(outputDir.resolve(DEFAULT_DEX_FILENAME).toString()),
+                    "inlining.Inlining",
+                    builder -> {},
+                    parameters.getRuntime().asDex().getVm());
+          } else {
+            assert parameters.isCfRuntime();
+            output =
+                ToolHelper.runJava(
+                        parameters.getRuntime().asCf(),
+                        Collections.singletonList("-noverify"),
+                        Collections.singletonList(outputDir),
+                        "inlining.Inlining")
+                    .stdout;
+          }
 
-    // Compare result with Java to make sure we have the same behavior.
-    ProcessResult javaResult = ToolHelper.runJava(getInputFile(), "inlining.Inlining");
-    assertEquals(0, javaResult.exitCode);
-    assertEquals(javaResult.stdout, output);
+          // Compare result with Java to make sure we have the same behavior.
+          ProcessResult javaResult = ToolHelper.runJava(getInputFile(), "inlining.Inlining");
+          assertEquals(0, javaResult.exitCode);
+          assertEquals(javaResult.stdout, output);
+        });
   }
 
   private void checkAbsentBooleanMethod(ClassSubject clazz, String name) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
index 43f777d..aa4dd9a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
@@ -41,6 +41,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualWithRefinedReceiverTest.class)
         .addKeepMainRule(MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
index c1839b9..3fb859c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
@@ -39,6 +39,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(WithStaticizerTest.class)
         .addKeepMainRule(MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
index 39bfa4c..1f8b7c1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
@@ -41,6 +41,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualNegativeTest.class)
         .addKeepMainRule(MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
index 2595df5..32d0307 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
@@ -41,6 +41,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualPositiveTest.class)
         .addKeepMainRule(MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index ffacc6a..c6f7772 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -64,6 +64,7 @@
 
   @Test
   public void testTrivial() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     Class<?> main = TrivialTestClass.class;
     Class<?>[] classes = {
         TrivialTestClass.class,
@@ -220,6 +221,7 @@
 
   @Test
   public void testInvalidatedRoot() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     Class<?> main = InvalidRootsTestClass.class;
     Class<?>[] classes = {
         InvalidRootsTestClass.class,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
index e0bd65e..2ed13fa 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
@@ -41,6 +41,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(enableInvokeSuperToInvokeVirtualRewriting);
     testForR8(parameters.getBackend())
         .addInnerClasses(ClassInliningOracleTest.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
index 9ebb411..a684ea3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -41,6 +41,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(SingleTargetAfterInliningTest.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index 0d81ff2..406e16d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -208,6 +208,7 @@
   @Test
   public void testR8() throws Exception {
     boolean isRelease = mode == CompilationMode.RELEASE;
+    expectThrowsWithHorizontalClassMergingIf(isRelease);
     boolean expectCallPresent = !isRelease;
     int expectedGetClassCount = isRelease ? 0 : 5;
     int expectedConstClassCount = isRelease ? (parameters.isCfRuntime() ? 8 : 6) : 1;
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 77c4810..df009b4 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,6 +14,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -267,6 +268,7 @@
 
   @Test
   public void testMoveToHost() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     Class<?> main = MoveToHostTestClass.class;
     Class<?>[] classes = {
         NeverInline.class,
@@ -281,7 +283,7 @@
         CandidateConflictField.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/typechecks/InstanceOfMethodSpecializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/typechecks/InstanceOfMethodSpecializationTest.java
index 5d13f66..4173bb2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/typechecks/InstanceOfMethodSpecializationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/typechecks/InstanceOfMethodSpecializationTest.java
@@ -50,6 +50,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(InstanceOfMethodSpecializationTest.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
index 828f4c0..d6ef4b2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
@@ -48,6 +48,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     String expected =
         StringUtils.joinLines(
             "Caught NullPointerException from testRewriteInvokeStaticToThrowNull",
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
index 41a1935..e14bb7a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
@@ -48,6 +48,7 @@
 
   @Test
   public void b139769782() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     String expectedOutput = StringUtils.lines("A#foo(B)", "A#foo(B, Object)");
 
     if (parameters.isCfRuntime() && !minification && !allowAccessModification) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java
index 88459a5..946523e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java
@@ -23,6 +23,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     String expectedOutput = StringUtils.lines("In A.m()", "Got NullPointerException");
     CodeInspector inspector =
         testForR8(Backend.DEX)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
index 7d3eb83..b3425b8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
@@ -37,6 +37,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     String expected =
         StringUtils.lines(
             "Factory.createStatic() -> null",
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
index af6296b..b453359 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
@@ -34,6 +34,7 @@
 
   @Test
   public void testUnusedAndUninstantiatedTypes() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(UnusedAndUninstantiatedTypesTest.class)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index 6fb984f..179333f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.google.common.base.Predicates;
 import java.util.Collection;
-import java.util.concurrent.atomic.AtomicInteger;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -37,6 +36,7 @@
 
   @Test
   public void testCompanionAndRegularObjects() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
     final String mainClassName = "class_staticizer.MainKt";
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index e5ce02c..33ffbca 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -82,6 +82,7 @@
 
   @Test
   public void testCompanionProperty_primitivePropertyIsAlwaysInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_usePrimitiveProp");
@@ -116,6 +117,7 @@
 
   @Test
   public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_usePrivateProp");
@@ -151,6 +153,7 @@
 
   @Test
   public void testCompanionProperty_internalPropertyIsAlwaysInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_useInternalProp");
@@ -185,6 +188,7 @@
 
   @Test
   public void testCompanionProperty_publicPropertyIsAlwaysInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_usePublicProp");
@@ -219,6 +223,7 @@
 
   @Test
   public void testCompanionLateInitProperty_privatePropertyIsAlwaysInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePrivateLateInitProp");
@@ -252,6 +257,7 @@
 
   @Test
   public void testCompanionLateInitProperty_internalPropertyIsAlwaysInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_useInternalLateInitProp");
@@ -276,6 +282,7 @@
 
   @Test
   public void testCompanionLateInitProperty_publicPropertyIsAlwaysInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePublicLateInitProp");
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index f572789..ec7d173 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -418,6 +418,7 @@
 
   @Test
   public void testCompanionProperty_primitivePropertyCannotBeInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp");
     runTest(
@@ -451,6 +452,7 @@
 
   @Test
   public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePrivateProp");
     runTest(
@@ -489,6 +491,7 @@
 
   @Test
   public void testCompanionProperty_internalPropertyCannotBeInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_useInternalProp");
     runTest(
@@ -521,6 +524,7 @@
 
   @Test
   public void testCompanionProperty_publicPropertyCannotBeInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePublicProp");
     runTest(
@@ -554,6 +558,7 @@
 
   @Test
   public void testCompanionProperty_privateLateInitPropertyIsAlwaysInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePrivateLateInitProp");
@@ -590,6 +595,7 @@
 
   @Test
   public void testCompanionProperty_internalLateInitPropertyCannotBeInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_useInternalLateInitProp");
@@ -617,6 +623,7 @@
 
   @Test
   public void testCompanionProperty_publicLateInitPropertyCannotBeInlined() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePublicLateInitProp");
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
index 2498467..3705c2d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
@@ -302,6 +302,7 @@
 
   @Test
   public void testTrivialKs() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final String mainClassName = "lambdas_kstyle_trivial.MainKt";
     runTest(
         "lambdas_kstyle_trivial",
@@ -383,6 +384,7 @@
 
   @Test
   public void testGenericsNoSignatureKs() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     final String mainClassName = "lambdas_kstyle_generics.MainKt";
     runTest(
         "lambdas_kstyle_generics",
@@ -411,6 +413,7 @@
 
   @Test
   public void testInnerClassesAndEnclosingMethodsKs() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     final String mainClassName = "lambdas_kstyle_generics.MainKt";
     runTest(
         "lambdas_kstyle_generics",
@@ -475,6 +478,7 @@
 
   @Test
   public void testTrivialJs() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final String mainClassName = "lambdas_jstyle_trivial.MainKt";
     runTest(
         "lambdas_jstyle_trivial",
@@ -524,6 +528,7 @@
 
   @Test
   public void testSingleton() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final String mainClassName = "lambdas_singleton.MainKt";
     runTest(
         "lambdas_singleton",
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
index 446d982..84ee93d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
@@ -35,6 +35,7 @@
 
   @Test
   public void testJStyleRunnable() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final String mainClassName = "lambdas_jstyle_runnable.MainKt";
     runTest("lambdas_jstyle_runnable", mainClassName, optionsModifier, null);
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index dec2ff2..0028070 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -41,6 +41,7 @@
 
   @Test
   public void testJstyleRunnable() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     final String folder = "lambdas_jstyle_runnable";
     final String mainClassName = "lambdas_jstyle_runnable.MainKt";
     final String implementer1ClassName = "lambdas_jstyle_runnable.Implementer1Kt";
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index 2a32355..fb89853 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -16,12 +15,12 @@
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DataResourceProvider.Visitor;
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.DataResourceConsumerForTesting;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -30,10 +29,8 @@
 import java.nio.charset.Charset;
 import java.nio.file.Path;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.junit.Test;
@@ -60,57 +57,6 @@
     this.backend = backend;
   }
 
-  public static class DataResourceConsumerForTesting implements DataResourceConsumer {
-
-    private final DataResourceConsumer inner;
-    private final Map<String, ImmutableList<String>> resources = new HashMap<>();
-
-    public DataResourceConsumerForTesting() {
-      this(null);
-    }
-
-    public DataResourceConsumerForTesting(DataResourceConsumer inner) {
-      this.inner = inner;
-    }
-
-    @Override
-    public void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
-      if (inner != null) {
-        inner.accept(directory, diagnosticsHandler);
-      }
-    }
-
-    @Override
-    public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
-      assertFalse(resources.containsKey(file.getName()));
-      try {
-        byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
-        String contents = new String(bytes, Charset.defaultCharset());
-        resources.put(file.getName(), ImmutableList.copyOf(contents.split(System.lineSeparator())));
-      } catch (Exception e) {
-        throw new RuntimeException(e);
-      }
-      if (inner != null) {
-        inner.accept(file, diagnosticsHandler);
-      }
-    }
-
-    @Override
-    public void finished(DiagnosticsHandler handler) {}
-
-    public ImmutableList<String> get(String name) {
-      return resources.get(name);
-    }
-
-    public boolean isEmpty() {
-      return size() == 0;
-    }
-
-    public int size() {
-      return resources.size();
-    }
-  }
-
   private static final ImmutableList<String> originalAllChangedResource =
       ImmutableList.of(
           "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 188e58d..8102aaf 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -19,11 +19,11 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.naming.AdaptResourceFileContentsTest.DataResourceConsumerForTesting;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.DataResourceConsumerForTesting;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.KeepingDiagnosticHandler;
 import com.android.tools.r8.utils.StringUtils;
@@ -112,6 +112,7 @@
 
   @Test
   public void testEnabled() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, null),
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index d0cb2bf..d84c94c 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -70,6 +70,7 @@
 
   @Test
   public void innerConstructsOuter() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     Class<?> clazz = com.android.tools.r8.regress.b69825683.innerconstructsouter.Outer.class;
     CodeInspector inspector =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
index 2f6574b..d912811 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
@@ -98,6 +98,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addProgramFiles(ToolHelper.getClassFilesForTestPackage(TestClass.class.getPackage()))
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
index 2000456..7435fe1 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
@@ -85,6 +85,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addProgramClasses(getClasses())
         .addProgramClassFileData(getTransformedClasses())
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
index 0460ce5..6e9ddce 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.OptionalBool;
-import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
@@ -29,8 +28,6 @@
 @RunWith(Parameterized.class)
 public class NestStaticMethodAccessWithIntermediateClassTest extends TestBase {
 
-  static final String EXPECTED = StringUtils.lines("A::bar");
-
   private final TestParameters parameters;
   private final boolean inSameNest;
 
@@ -87,6 +84,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addProgramClasses(getClasses())
         .addProgramClassFileData(getTransformedClasses())
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
index 5e2743c..bc9973b 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
@@ -87,6 +87,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(!inSameNest);
     testForR8(parameters.getBackend())
         .addProgramClasses(getClasses())
         .addProgramClassFileData(getTransformedClasses())
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
index 4aff595..0deacd4 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.OptionalBool;
-import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
@@ -28,8 +27,6 @@
 @RunWith(Parameterized.class)
 public class NestVirtualMethodAccessWithIntermediateClassTest extends TestBase {
 
-  static final String EXPECTED = StringUtils.lines("A::bar");
-
   private final TestParameters parameters;
   private final boolean inSameNest;
 
@@ -86,6 +83,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addProgramClasses(getClasses())
         .addProgramClassFileData(getTransformedClasses())
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
index eb88c69..8ae84a0 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
@@ -79,6 +79,7 @@
 
   @Test
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    expectThrowsWithHorizontalClassMergingIf(parameters.isCfRuntime());
     testForR8(parameters.getBackend())
         .addInnerClasses(MultipleImplementsTest.class)
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
index 5628ea2..829a26b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
@@ -85,6 +85,7 @@
 
   @Test
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    expectThrowsWithHorizontalClassMergingIf(parameters.isCfRuntime());
     testForR8(parameters.getBackend())
         .addInnerClasses(SubTypeMissingOverridesTest.class)
         .enableNoVerticalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
index c1f2301..0afee04 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
@@ -86,6 +86,7 @@
 
   @Test
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    expectThrowsWithHorizontalClassMergingIf(parameters.isCfRuntime());
     testForR8(parameters.getBackend())
         .addInnerClasses(TargetInDefaultMethodTest.class)
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
index b299b76..943e3b3 100644
--- a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
@@ -53,6 +53,7 @@
 
   @Test
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    expectThrowsWithHorizontalClassMergingIf(parameters.isDexRuntime());
     testForR8(parameters.getBackend())
         .addInnerClasses(JavaScriptScriptEngineTest.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassTest.java
new file mode 100644
index 0000000..94d7781
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassTest.java
@@ -0,0 +1,134 @@
+// 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.rewrite.serviceloaders;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.rewrite.serviceloaders.MissingServiceImplementationClassTest.Service;
+import com.android.tools.r8.rewrite.serviceloaders.MissingServiceImplementationClassTest.ServiceImpl;
+import com.android.tools.r8.utils.DataResourceConsumerForTesting;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import java.util.ServiceLoader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// Test where the service class and the service implementation class are missing.
+@RunWith(Parameterized.class)
+public class MissingServiceClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MissingServiceClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
+                AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName(),
+                Origin.unknown()))
+        .addOptionsModification(o -> o.dataResourceConsumer = dataResourceConsumer)
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectDiagnosticMessages(
+            inspector -> {
+              inspector.assertWarningsCount(2);
+              inspector.assertAllWarningsMatch(
+                  diagnosticMessage(
+                      anyOf(
+                          containsString(
+                              "Unexpected reference to missing service class: "
+                                  + AppServices.SERVICE_DIRECTORY_NAME
+                                  + Service.class.getTypeName()
+                                  + "."),
+                          containsString(
+                              "Unexpected reference to missing service implementation class in "
+                                  + AppServices.SERVICE_DIRECTORY_NAME
+                                  + Service.class.getTypeName()
+                                  + ": "
+                                  + ServiceImpl.class.getTypeName()
+                                  + "."))));
+            })
+        .apply(this::configureRunClasspath)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithEmptyOutput();
+
+    inspectResource(
+        dataResourceConsumer.get(AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName()));
+  }
+
+  private void inspectResource(List<String> contents) {
+    assertNotNull(contents);
+    assertEquals(1, contents.size());
+    assertEquals(ServiceImpl.class.getTypeName(), contents.get(0));
+  }
+
+  private void configureRunClasspath(R8TestCompileResult compileResult) throws Exception {
+    if (parameters.isCfRuntime()) {
+      compileResult.addRunClasspathClasses(Service.class, ServiceImpl.class);
+    } else {
+      compileResult.addRunClasspathFiles(
+          testForD8(temp)
+              .addProgramClasses(Service.class, ServiceImpl.class)
+              .setMinApi(parameters.getApiLevel())
+              .compile()
+              .writeToZip());
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws ClassNotFoundException {
+      for (Object service : ServiceLoader.load(getServiceClass())) {
+        System.out.println(service.toString());
+      }
+    }
+
+    static Class<?> getServiceClass() throws ClassNotFoundException {
+      // Get the Service class without accessing it directly. Accessing it directly would cause a
+      // compilation error due the the class being absent. This can be avoided by adding -dontwarn,
+      // but then R8 don't report a warning for the incorrect resource in META-INF/services, which
+      // this is trying to test.
+      return Class.forName(
+          "com.android.tools.r8.rewrite.serviceloaders.MissingServiceClassTest$Service");
+    }
+  }
+
+  public interface Service {}
+
+  public static class ServiceImpl implements Service {
+
+    @Override
+    public String toString() {
+      return "Hello world!";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassWithFeatureTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassWithFeatureTest.java
new file mode 100644
index 0000000..f36c122
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassWithFeatureTest.java
@@ -0,0 +1,75 @@
+// 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.rewrite.serviceloaders;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ServiceLoader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// Test where a service implementation class is missing in presence of feature splits.
+@RunWith(Parameterized.class)
+public class MissingServiceClassWithFeatureTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public MissingServiceClassWithFeatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, ServiceImpl.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(FeatureClass.class)
+        .addKeepRules("-dontwarn " + Service.class.getTypeName())
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines("java.lang.Object").getBytes(),
+                AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName(),
+                Origin.unknown()))
+        .addFeatureSplit(FeatureClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      for (Service service : ServiceLoader.load(Service.class, TestClass.class.getClassLoader())) {
+        service.greet();
+      }
+    }
+  }
+
+  public interface Service {
+
+    void greet();
+  }
+
+  public static class ServiceImpl implements Service {
+
+    @Override
+    public void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+
+  public static class FeatureClass {}
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassTest.java
new file mode 100644
index 0000000..7f3e416
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassTest.java
@@ -0,0 +1,180 @@
+// 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.rewrite.serviceloaders;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.rewrite.serviceloaders.MissingServiceClassTest.Service;
+import com.android.tools.r8.rewrite.serviceloaders.MissingServiceClassTest.ServiceImpl;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DataResourceConsumerForTesting;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.io.ByteStreams;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// Test where a service implementation class is missing.
+@RunWith(Parameterized.class)
+public class MissingServiceImplementationClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MissingServiceImplementationClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Box<DataResourceConsumerForTesting> dataResourceConsumer = new Box<>();
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(TestClass.class, Service.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(Service.class)
+            .addDataEntryResources(
+                DataEntryResource.fromBytes(
+                    StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
+                    AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName(),
+                    Origin.unknown()))
+            .addOptionsModification(
+                options -> {
+                  dataResourceConsumer.set(
+                      new DataResourceConsumerForTesting(options.dataResourceConsumer));
+                  options.dataResourceConsumer = dataResourceConsumer.get();
+                })
+            .allowDiagnosticWarningMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                inspector -> {
+                  inspector.assertWarningsCount(1);
+                  inspector.assertAllWarningsMatch(
+                      diagnosticMessage(
+                          containsString(
+                              "Unexpected reference to missing service implementation class in "
+                                  + AppServices.SERVICE_DIRECTORY_NAME
+                                  + Service.class.getTypeName()
+                                  + ": "
+                                  + ServiceImpl.class.getTypeName()
+                                  + ".")));
+                });
+
+    CodeInspector inspector = compileResult.inspector();
+    ClassSubject serviceClassSubject = inspector.clazz(Service.class);
+    assertThat(serviceClassSubject, isPresent());
+
+    // Verify that ServiceImpl was not removed from META-INF/services/[...]Service.
+    inspectResource(
+        dataResourceConsumer.get().get("META-INF/services/" + serviceClassSubject.getFinalName()));
+
+    // Execution fails since META-INF/services/[...]Service is referring to a missing class.
+    compileResult
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(ServiceConfigurationError.class);
+
+    // Verify that it is still possible to inject a new implementation of Service after the
+    // compilation.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(ServiceImpl.class)
+        .addClasspathClasses(Service.class)
+        .addKeepAllClassesRule()
+        .addApplyMapping(compileResult.getProguardMap())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(
+            transformServiceDeclarationInProgram(
+                compileResult.writeToZip(),
+                AppServices.SERVICE_DIRECTORY_NAME + serviceClassSubject.getFinalName(),
+                ServiceImpl.class.getTypeName()))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspectResource(List<String> contents) {
+    assertNotNull(contents);
+    assertEquals(1, contents.size());
+    assertEquals(ServiceImpl.class.getTypeName(), contents.get(0));
+  }
+
+  private Path transformServiceDeclarationInProgram(Path program, String fileName, String contents)
+      throws Exception {
+    Path newProgram = temp.newFolder().toPath().resolve("program.jar");
+    try (ZipOutputStream outputStream = new ZipOutputStream(Files.newOutputStream(newProgram))) {
+      try (ZipInputStream inputStream = new ZipInputStream(Files.newInputStream(program))) {
+        ZipEntry next = inputStream.getNextEntry();
+        while (next != null) {
+          outputStream.putNextEntry(new ZipEntry(next.getName()));
+          if (next.getName().equals(fileName)) {
+            outputStream.write(contents.getBytes());
+          } else {
+            outputStream.write(ByteStreams.toByteArray(inputStream));
+          }
+          outputStream.closeEntry();
+          next = inputStream.getNextEntry();
+        }
+      }
+    }
+    return newProgram;
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      for (Object object : ServiceLoader.load(getServiceClass())) {
+        if (object instanceof Service) {
+          Service service = (Service) object;
+          service.greet();
+        }
+      }
+    }
+
+    static Class<?> getServiceClass() {
+      return System.currentTimeMillis() >= 0 ? Service.class : Object.class;
+    }
+  }
+
+  public interface Service {
+
+    void greet();
+  }
+
+  public static class ServiceImpl implements Service {
+
+    @NeverInline
+    public void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassWithFeatureTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassWithFeatureTest.java
new file mode 100644
index 0000000..b3f120c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassWithFeatureTest.java
@@ -0,0 +1,82 @@
+// 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.rewrite.serviceloaders;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.rewrite.serviceloaders.MissingServiceImplementationClassTest.Service;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ServiceLoader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// Test where a service implementation class is missing in presence of feature splits.
+@RunWith(Parameterized.class)
+public class MissingServiceImplementationClassWithFeatureTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public MissingServiceImplementationClassWithFeatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, Service.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(FeatureClass.class)
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines("MissingClass").getBytes(),
+                AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName(),
+                Origin.unknown()))
+        .addFeatureSplit(FeatureClass.class)
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectDiagnosticMessages(
+            inspector -> {
+              inspector.assertWarningsCount(1);
+              inspector.assertAllWarningsMatch(
+                  diagnosticMessage(
+                      containsString(
+                          "Unexpected reference to missing service implementation class in "
+                              + AppServices.SERVICE_DIRECTORY_NAME
+                              + Service.class.getTypeName()
+                              + ": MissingClass.")));
+            });
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      for (Service service : ServiceLoader.load(Service.class, TestClass.class.getClassLoader())) {
+        service.greet();
+      }
+    }
+  }
+
+  public interface Service {
+
+    void greet();
+  }
+
+  public static class FeatureClass {}
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/ServiceWithoutResourceFileAtCompileTimeTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/ServiceWithoutResourceFileAtCompileTimeTest.java
new file mode 100644
index 0000000..1ea55b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/ServiceWithoutResourceFileAtCompileTimeTest.java
@@ -0,0 +1,131 @@
+// 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.rewrite.serviceloaders;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.io.ByteStreams;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ServiceLoader;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// Test where the resource META-INF/services/Service for a service class Service is missing.
+//
+// The program succeeds with the expected output when META-INF/services/Service is bundled with the
+// application after the compilation.
+@RunWith(Parameterized.class)
+public class ServiceWithoutResourceFileAtCompileTimeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ServiceWithoutResourceFileAtCompileTimeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(TestClass.class, Service.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(Service.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    CodeInspector inspector = compileResult.inspector();
+    ClassSubject serviceClassSubject = inspector.clazz(Service.class);
+    assertThat(serviceClassSubject, isPresent());
+
+    // Execution succeeds with the empty output since META-INF/services/[...]Service is missing.
+    compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithEmptyOutput();
+
+    // Verify that it is still possible to inject a new implementation of Service after the
+    // compilation.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(ServiceImpl.class)
+        .addClasspathClasses(Service.class)
+        .addKeepAllClassesRule()
+        .addApplyMapping(compileResult.getProguardMap())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(
+            injectServiceDeclarationInProgram(
+                compileResult.writeToZip(),
+                AppServices.SERVICE_DIRECTORY_NAME + serviceClassSubject.getFinalName(),
+                ServiceImpl.class.getTypeName()))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private Path injectServiceDeclarationInProgram(Path program, String fileName, String contents)
+      throws Exception {
+    Path newProgram = temp.newFolder().toPath().resolve("program.jar");
+    try (ZipOutputStream outputStream = new ZipOutputStream(Files.newOutputStream(newProgram))) {
+      try (ZipInputStream inputStream = new ZipInputStream(Files.newInputStream(program))) {
+        ZipEntry next = inputStream.getNextEntry();
+        while (next != null) {
+          outputStream.putNextEntry(new ZipEntry(next.getName()));
+          outputStream.write(ByteStreams.toByteArray(inputStream));
+          outputStream.closeEntry();
+          next = inputStream.getNextEntry();
+        }
+      }
+      outputStream.putNextEntry(new ZipEntry(fileName));
+      outputStream.write(contents.getBytes());
+      outputStream.closeEntry();
+    }
+    return newProgram;
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      for (Object object : ServiceLoader.load(getServiceClass())) {
+        if (object instanceof Service) {
+          Service service = (Service) object;
+          service.greet();
+        }
+      }
+    }
+
+    static Class<?> getServiceClass() {
+      return System.currentTimeMillis() >= 0 ? Service.class : Object.class;
+    }
+  }
+
+  public interface Service {
+
+    void greet();
+  }
+
+  public static class ServiceImpl implements Service {
+
+    @NeverInline
+    public void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java b/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java
index 2f95543..4d65424 100644
--- a/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java
@@ -40,6 +40,7 @@
 
   @Test
   public void testCompat() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     R8TestCompileResult compileResult =
         testForR8Compat(parameters.getBackend())
             .addInnerClasses(AbstractMethodOnNonAbstractClassTest.class)
diff --git a/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
index 382f515..b85fe68 100644
--- a/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
@@ -37,6 +37,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .enableInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index fd676a3..6bd40a0 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -100,6 +100,7 @@
 
   @Test
   public void testWithMembersPresentAnnotation() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(Backend.CF)
         .addProgramFiles(R8_JAR)
         .addKeepRules("-keepclasseswithmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
@@ -183,6 +184,7 @@
 
   @Test
   public void testConditionalEqualsKeepClassMembers() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     GraphInspector referenceInspector =
         testForR8(Backend.CF)
             .enableGraphInspector()
diff --git a/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java
index f813bf2..42dab36 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java
@@ -38,6 +38,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .enableInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
index e23b80b..b21fc5f 100644
--- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -19,11 +19,11 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.AppServices;
-import com.android.tools.r8.naming.AdaptResourceFileContentsTest.DataResourceConsumerForTesting;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DataResourceConsumerForTesting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnFieldsTest.java
index edd818e..d0e3b9e 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnFieldsTest.java
@@ -45,6 +45,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8Compat(backend)
         .enableNeverClassInliningAnnotations()
         .addProgramClasses(CLASSES)
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
index 86a77a5..816c073 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
@@ -141,6 +141,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(config != TestConfig.NON_SPECIFIC_RULES_ALL);
     testForR8(parameters.getBackend())
         .addInnerClasses(AssumenosideeffectsPropagationTest.class)
         .addKeepMainRule(MAIN)
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithSuperCallTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithSuperCallTest.java
index ba5da12..ecf26e9 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithSuperCallTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithSuperCallTest.java
@@ -73,6 +73,7 @@
 
   @Test
   public void testR8() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(AssumenosideeffectsPropagationWithSuperCallTest.class)
         .addKeepMainRule(MAIN)
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
index b30a14d..47f8b62 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
@@ -44,6 +44,8 @@
 
   @Test
   public void testKeeprules() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        getFrontend() != Frontend.JAR || getParameters().isDexRuntime());
     runTest(
         TreeShaking12Test::shaking12OnlyInstantiatedClassesHaveConstructors,
         null,
@@ -53,6 +55,8 @@
 
   @Test
   public void testKeeprulesprintusage() throws Exception {
+    expectThrowsWithHorizontalClassMergingIf(
+        getFrontend() != Frontend.JAR || getParameters().isDexRuntime());
     runTest(
         null,
         null,
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index d91a2fd..a1a2748 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -39,6 +39,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     runTest(
         TreeShaking18Test::unusedRemoved,
         null,
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
index b008254..a82fede 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
@@ -37,6 +37,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     runTest(
         null,
         null,
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
index 08dfc6a..97ad730 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
@@ -37,11 +37,13 @@
 
   @Test
   public void testKeeprules() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     runTest(null, null, null, ImmutableList.of("src/test/examples/inlining/keep-rules.txt"));
   }
 
   @Test
   public void testKeeprulesdiscard() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     // On the cf backend, we don't inline into constructors, see: b/136250031
     List<String> keepRules =
         getParameters().isCfRuntime()
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
index b8151d6..f703896 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
@@ -38,6 +38,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     runTest(null, null, null, ImmutableList.of("src/test/examples/minification/keep-rules.txt"));
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index a686567..8ab404c 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -82,8 +82,9 @@
 
   @Test
   public void testNoIncludesDescriptorClasses() throws Exception {
-    for (Class mainClass : mainClasses) {
-      List<Class> allClasses = new ArrayList<>(applicationClasses);
+    expectThrowsWithHorizontalClassMerging();
+    for (Class<?> mainClass : mainClasses) {
+      List<Class<?>> allClasses = new ArrayList<>(applicationClasses);
       allClasses.add(mainClass);
 
       Path proguardConfig = writeTextToTempFile(
@@ -160,7 +161,8 @@
 
     @Test
     public void testKeepClassMemberNames() throws Exception {
-      for (Class mainClass : mainClasses) {
+    expectThrowsWithHorizontalClassMerging();
+    for (Class<?> mainClass : mainClasses) {
         Path proguardConfig = writeTextToTempFile(
             keepMainProguardConfiguration(mainClass),
             // same as -keepclassmembers,allowshrinking,includedescriptorclasses
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
index cd6e4a3..367bfbc 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
@@ -45,6 +45,7 @@
 
   @Test
   public void test() throws Throwable {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(Backend.CF)
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
         .addKeepRuleFiles(MAIN_KEEP)
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
index bc12cfc..908f9f7 100644
--- a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
@@ -37,6 +37,7 @@
 
   @Test
   public void test() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(LibraryMethodOverrideTest.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index 41533ec..8d56292 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -50,6 +50,7 @@
 
   @Test
   public void testDefaultInlining() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     CodeInspector inspector =
         runTest(
             ImmutableList.of(
@@ -78,6 +79,7 @@
 
   @Test
   public void testNeverInline() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     CodeInspector inspector =
         runTest(
             ImmutableList.of(
@@ -105,6 +107,7 @@
 
   @Test
   public void testForceInline() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     CodeInspector inspector =
         runTest(
             ImmutableList.of(
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index fa627ef..4779b8e 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -162,6 +162,14 @@
 
   /** Unconditionally replace the implements clause of a class. */
   public ClassFileTransformer setImplements(Class<?>... interfaces) {
+    return setImplementsClassDescriptors(
+        Arrays.stream(interfaces)
+            .map(clazz -> DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()))
+            .toArray(String[]::new));
+  }
+
+  /** Unconditionally replace the implements clause of a class. */
+  public ClassFileTransformer setImplementsClassDescriptors(String... classDescriptors) {
     return addClassTransformer(
         new ClassTransformer() {
           @Override
@@ -178,8 +186,8 @@
                 name,
                 signature,
                 superName,
-                Arrays.stream(interfaces)
-                    .map(clazz -> DescriptorUtils.getBinaryNameFromJavaType(clazz.getTypeName()))
+                Arrays.stream(classDescriptors)
+                    .map(DescriptorUtils::getBinaryNameFromDescriptor)
                     .toArray(String[]::new));
           }
         });
diff --git a/src/test/java/com/android/tools/r8/utils/AndroidJarPresenceTest.java b/src/test/java/com/android/tools/r8/utils/AndroidJarPresenceTest.java
new file mode 100644
index 0000000..e84aaa2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/AndroidJarPresenceTest.java
@@ -0,0 +1,35 @@
+// 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.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AndroidJarPresenceTest extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public AndroidJarPresenceTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void test() throws Exception {
+    for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+      assertEquals(ToolHelper.shouldHaveAndroidJar(apiLevel), ToolHelper.hasAndroidJar(apiLevel));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/DataResourceConsumerForTesting.java b/src/test/java/com/android/tools/r8/utils/DataResourceConsumerForTesting.java
new file mode 100644
index 0000000..77274e6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/DataResourceConsumerForTesting.java
@@ -0,0 +1,68 @@
+// 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.utils;
+
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DataResourceConsumerForTesting implements DataResourceConsumer {
+
+  private final DataResourceConsumer inner;
+  private final Map<String, ImmutableList<String>> resources = new HashMap<>();
+
+  public DataResourceConsumerForTesting() {
+    this(null);
+  }
+
+  public DataResourceConsumerForTesting(DataResourceConsumer inner) {
+    this.inner = inner;
+  }
+
+  @Override
+  public void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+    if (inner != null) {
+      inner.accept(directory, diagnosticsHandler);
+    }
+  }
+
+  @Override
+  public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+    assertFalse(resources.containsKey(file.getName()));
+    try {
+      byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+      String contents = new String(bytes, Charset.defaultCharset());
+      resources.put(file.getName(), ImmutableList.copyOf(contents.split(System.lineSeparator())));
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+    if (inner != null) {
+      inner.accept(file, diagnosticsHandler);
+    }
+  }
+
+  @Override
+  public void finished(DiagnosticsHandler handler) {}
+
+  public ImmutableList<String> get(String name) {
+    return resources.get(name);
+  }
+
+  public boolean isEmpty() {
+    return size() == 0;
+  }
+
+  public int size() {
+    return resources.size();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/ReflectiveBuildPathUtils.java b/src/test/java/com/android/tools/r8/utils/ReflectiveBuildPathUtils.java
new file mode 100644
index 0000000..ed991f2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/ReflectiveBuildPathUtils.java
@@ -0,0 +1,168 @@
+// 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.utils;
+
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.Iterables;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class ReflectiveBuildPathUtils {
+
+  public interface PackageUtils {
+    String getPackageName() throws Exception;
+
+    Path getPackagePath() throws Exception;
+
+    PackageUtils getParentPackageUtils() throws Exception;
+
+    Iterable<PackageUtils> getAllPackageUtils();
+  }
+
+  public interface ClassUtils {
+    String getClassName() throws Exception;
+
+    String getSimpleClassName() throws Exception;
+
+    Path getClassPath() throws Exception;
+  }
+
+  public abstract static class ExamplesRootPackage implements PackageUtils {
+    public abstract Path getPackagePath();
+
+    @Override
+    public String getPackageName() {
+      return "";
+    }
+
+    @Override
+    public PackageUtils getParentPackageUtils() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Iterable<PackageUtils> getAllPackageUtils() {
+      return instantiateAllPackageUtils(getClass());
+    }
+  }
+
+  public abstract static class ExamplesJava11RootPackage extends ExamplesRootPackage {
+    @Override
+    public Path getPackagePath() {
+      return Paths.get(ToolHelper.EXAMPLES_JAVA11_BUILD_DIR);
+    }
+  }
+
+  public abstract static class ExamplesPackage implements PackageUtils {
+    public List<String> getName() {
+      return Collections.singletonList(getClass().getSimpleName());
+    }
+
+    @Override
+    public PackageUtils getParentPackageUtils() throws Exception {
+      return (PackageUtils) getClass().getDeclaringClass().getConstructor().newInstance();
+    }
+
+    @Override
+    public Path getPackagePath() throws Exception {
+      Path path = getParentPackageUtils().getPackagePath();
+      for (String folder : getName()) {
+        path = path.resolve(folder);
+      }
+      return path;
+    }
+
+    @Override
+    public String getPackageName() throws Exception {
+      return getParentPackageUtils().getPackageName() + String.join(".", getName()) + ".";
+    }
+
+    @Override
+    public Iterable<PackageUtils> getAllPackageUtils() {
+      return instantiateAllPackageUtils(getClass());
+    }
+  }
+
+  public static class ExamplesClass implements PackageUtils, ClassUtils {
+    @Override
+    public PackageUtils getParentPackageUtils() throws Exception {
+      return (PackageUtils) getClass().getDeclaringClass().getConstructor().newInstance();
+    }
+
+    public String getSimpleClassName() throws Exception {
+      Object parent = getClass().getDeclaringClass().getConstructor().newInstance();
+      if (parent instanceof ClassUtils) {
+        return ((ClassUtils) parent).getSimpleClassName() + "$" + getClass().getSimpleName();
+      } else {
+        return getClass().getSimpleName();
+      }
+    }
+
+    @Override
+    public String getClassName() throws Exception {
+      return getParentPackageUtils().getPackageName() + getSimpleClassName();
+    }
+
+    @Override
+    public Path getClassPath() throws Exception {
+      return getParentPackageUtils().getPackagePath().resolve(getSimpleClassName() + ".class");
+    }
+
+    @Override
+    public String getPackageName() throws Exception {
+      return getParentPackageUtils().getPackageName();
+    }
+
+    @Override
+    public Path getPackagePath() throws Exception {
+      return getParentPackageUtils().getPackagePath();
+    }
+
+    @Override
+    public Iterable<PackageUtils> getAllPackageUtils() {
+      return instantiateAllPackageUtils(getClass());
+    }
+  }
+
+  public static Iterable<PackageUtils> instantiateAllPackageUtils(Class<?> parentClazz) {
+    Collection<PackageUtils> children = instantiatePackageUtils(parentClazz);
+    return Iterables.concat(
+        children,
+        Iterables.concat(Iterables.transform(children, PackageUtils::getAllPackageUtils)));
+  }
+
+  public static Collection<PackageUtils> instantiatePackageUtils(Class<?> parentClazz) {
+    Collection<PackageUtils> packageUtils = new ArrayList<>();
+    for (Class<?> clazz : parentClazz.getDeclaredClasses()) {
+      try {
+        Object obj = clazz.getConstructor().newInstance();
+        if (obj instanceof PackageUtils) {
+          packageUtils.add((PackageUtils) obj);
+        }
+      } catch (Exception ex) {
+      }
+    }
+    return packageUtils;
+  }
+
+  public static String resolveClassName(Class<? extends ExamplesClass> clazz) throws Exception {
+    return clazz.getConstructor().newInstance().getClassName();
+  }
+
+  public static Collection<Path> allClassFiles(Class<? extends ExamplesRootPackage> clazz)
+      throws Exception {
+    Collection<Path> classFiles = new ArrayList<>();
+    for (PackageUtils util : clazz.getConstructor().newInstance().getAllPackageUtils()) {
+      if (util instanceof ClassUtils) {
+        classFiles.add(((ClassUtils) util).getClassPath());
+      }
+    }
+    return classFiles;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 978db99..6160dc6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -47,6 +47,11 @@
   }
 
   @Override
+  public MethodSubject uniqueMethodWithFinalName(String name) {
+    return new AbsentMethodSubject();
+  }
+
+  @Override
   public void forAllFields(Consumer<FoundFieldSubject> inspection) {}
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 6c7368b..60203c1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -100,6 +100,8 @@
 
   public abstract MethodSubject uniqueMethodWithName(String name);
 
+  public abstract MethodSubject uniqueMethodWithFinalName(String name);
+
   public MethodSubject mainMethod() {
     return method("void", "main", ImmutableList.of("java.lang.String[]"));
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index f42bc3e..ab1d03b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -146,6 +146,18 @@
   }
 
   @Override
+  public MethodSubject uniqueMethodWithFinalName(String name) {
+    MethodSubject methodSubject = null;
+    for (FoundMethodSubject candidate : allMethods()) {
+      if (candidate.getFinalName().equals(name)) {
+        assert methodSubject == null;
+        methodSubject = candidate;
+      }
+    }
+    return methodSubject != null ? methodSubject : new AbsentMethodSubject();
+  }
+
+  @Override
   public void forAllFields(Consumer<FoundFieldSubject> inspection) {
     forAllInstanceFields(inspection);
     forAllStaticFields(inspection);
diff --git a/tools/test.py b/tools/test.py
index 27f15a8..c949fb3 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -163,11 +163,6 @@
   (options, args) = ParseOptions()
 
   if utils.is_bot():
-    if options.horizontal_class_merging:
-      # This flag is in preparation of running horizontal class merging
-      # but currently is the same as the default tests. Don't run to
-      # save resources on the bots.
-      return 0
     gradle.RunGradle(['--no-daemon', 'clean'])
 
   gradle_args = ['--stacktrace']